Added a few context menus

This commit is contained in:
Sasha Koshka 2023-04-21 00:52:34 -04:00
parent f88268bb0e
commit 6622799019
4 changed files with 163 additions and 76 deletions

View File

@ -40,7 +40,6 @@ func (window *window) handleExpose (
event xevent.ExposeEvent,
) {
_, region := window.compressExpose(*event.ExposeEvent)
window.system.afterEvent()
window.pushRegion(region)
}
@ -79,8 +78,6 @@ func (window *window) handleConfigureNotify (
window.child.Invalidate()
window.child.InvalidateLayout()
}
window.system.afterEvent()
}
}
@ -140,8 +137,6 @@ func (window *window) handleKeyPress (
focused, ok := window.focused.element.(tomo.KeyboardTarget)
if ok { focused.HandleKeyDown(key, modifiers) }
}
window.system.afterEvent()
}
func (window *window) handleKeyRelease (
@ -174,8 +169,6 @@ func (window *window) handleKeyRelease (
if window.focused != nil {
focused, ok := window.focused.element.(tomo.KeyboardTarget)
if ok { focused.HandleKeyUp(key, modifiers) }
window.system.afterEvent()
}
}
@ -220,8 +213,6 @@ func (window *window) handleButtonPress (
}
underneath.forMouseTargetContainers(callback)
}
window.system.afterEvent()
}
func (window *window) handleButtonRelease (
@ -252,8 +243,6 @@ func (window *window) handleButtonRelease (
}
dragging.forMouseTargetContainers(callback)
}
window.system.afterEvent()
}
func (window *window) handleMotionNotify (
@ -279,8 +268,6 @@ func (window *window) handleMotionNotify (
child.HandleMotion(image.Pt(x, y))
}
}
window.system.afterEvent()
}
func (window *window) handleSelectionNotify (

View File

@ -67,12 +67,12 @@ func (backend *Backend) Run () (err error) {
<- pingAfter
case callback := <- backend.doChannel:
callback()
for _, window := range backend.windows {
window.system.afterEvent()
}
case <- pingQuit:
return
}
for _, window := range backend.windows {
window.system.afterEvent()
}
}
}

View File

@ -1,7 +1,10 @@
package elements
import "image"
import "golang.org/x/image/math/fixed"
import "git.tebibyte.media/sashakoshka/tomo"
import "git.tebibyte.media/sashakoshka/tomo/data"
import "git.tebibyte.media/sashakoshka/tomo/input"
import "git.tebibyte.media/sashakoshka/tomo/canvas"
import "git.tebibyte.media/sashakoshka/tomo/textdraw"
import "git.tebibyte.media/sashakoshka/tomo/default/theme"
@ -48,6 +51,32 @@ func (element *Label) Entity () tomo.Entity {
return element.entity
}
// Draw causes the element to draw to the specified destination canvas.
func (element *Label) Draw (destination canvas.Canvas) {
bounds := element.entity.Bounds()
if element.wrap {
element.drawer.SetMaxWidth(bounds.Dx())
element.drawer.SetMaxHeight(bounds.Dy())
}
element.entity.DrawBackground(destination)
textBounds := element.drawer.LayoutBounds()
foreground := element.theme.Color (
tomo.ColorForeground,
tomo.State { })
element.drawer.Draw(destination, foreground, bounds.Min.Sub(textBounds.Min))
}
// Copy copies the label's textto the clipboard.
func (element *Label) Copy () {
window := element.entity.Window()
if window != nil {
window.Copy(data.Bytes(data.MimePlain, []byte(element.text)))
}
}
// EmCollapse forces a minimum width and height upon the label. The width is
// measured in emspaces, and the height is measured in lines. If a zero value is
// given for a dimension, its minimum will be determined by the label's content.
@ -122,22 +151,42 @@ func (element *Label) SetConfig (new tomo.Config) {
element.entity.Invalidate()
}
// Draw causes the element to draw to the specified destination canvas.
func (element *Label) Draw (destination canvas.Canvas) {
bounds := element.entity.Bounds()
if element.wrap {
element.drawer.SetMaxWidth(bounds.Dx())
element.drawer.SetMaxHeight(bounds.Dy())
func (element *Label) HandleMouseDown (
position image.Point,
button input.Button,
modifiers input.Modifiers,
) {
if button == input.ButtonRight {
element.contextMenu(position)
}
}
func (element *Label) HandleMouseUp (
position image.Point,
button input.Button,
modifiers input.Modifiers,
) { }
func (element *Label) contextMenu (position image.Point) {
window := element.entity.Window()
menu, err := window.NewMenu(image.Rectangle { position, position })
if err != nil { return }
closeAnd := func (callback func ()) func () {
return func () { callback(); menu.Close() }
}
element.entity.DrawBackground(destination)
copyButton := NewButton("Copy")
copyButton.ShowText(false)
copyButton.SetIcon(tomo.IconCopy)
copyButton.OnClick(closeAnd(element.Copy))
textBounds := element.drawer.LayoutBounds()
foreground := element.theme.Color (
tomo.ColorForeground,
tomo.State { })
element.drawer.Draw(destination, foreground, bounds.Min.Sub(textBounds.Min))
menu.Adopt (NewHBox (
SpaceNone,
copyButton,
))
copyButton.Focus()
menu.Show()
}
func (element *Label) updateMinimumSize () {

View File

@ -151,7 +151,8 @@ func (element *TextBox) HandleMouseDown (
if !element.Enabled() { return }
element.Focus()
if button == input.ButtonLeft {
switch button {
case input.ButtonLeft:
runeIndex := element.atPosition(position)
if runeIndex == -1 { return }
@ -165,6 +166,18 @@ func (element *TextBox) HandleMouseDown (
}
element.entity.Invalidate()
case input.ButtonRight:
element.contextMenu(position)
}
}
func (element *TextBox) HandleMouseUp (
position image.Point,
button input.Button,
modifiers input.Modifiers,
) {
if button == input.ButtonLeft {
element.dragging = 0
}
}
@ -217,23 +230,12 @@ func (element *TextBox) atPosition (position image.Point) int {
fixedutil.Pt(position.Sub(offset).Add(textBoundsMin)))
}
func (element *TextBox) HandleMouseUp (
position image.Point,
button input.Button,
modifiers input.Modifiers,
) {
if button == input.ButtonLeft {
element.dragging = 0
}
}
func (element *TextBox) HandleKeyDown(key input.Key, modifiers input.Modifiers) {
if element.onKeyDown != nil && element.onKeyDown(key, modifiers) {
return
}
scrollMemory := element.scroll
altered := true
textChanged := false
switch {
case key == input.KeyEnter:
@ -269,6 +271,8 @@ func (element *TextBox) HandleKeyDown(key input.Key, modifiers input.Modifiers)
element.dot,
modifiers.Control)
}
element.scrollToCursor()
element.entity.Invalidate()
case key == input.KeyRight:
if modifiers.Shift {
@ -282,38 +286,24 @@ func (element *TextBox) HandleKeyDown(key input.Key, modifiers input.Modifiers)
element.dot,
modifiers.Control)
}
element.scrollToCursor()
element.entity.Invalidate()
case key == 'a' && modifiers.Control:
element.dot.Start = 0
element.dot.End = len(element.text)
element.scrollToCursor()
element.entity.Invalidate()
case key == 'x' && modifiers.Control:
var lifted []rune
element.text, element.dot, lifted = textmanip.Lift (
element.text,
element.dot)
if lifted != nil {
element.clipboardPut(lifted)
textChanged = true
}
case key == 'x' && modifiers.Control: element.Cut()
case key == 'c' && modifiers.Control: element.Copy()
case key == 'v' && modifiers.Control: element.Paste()
case key == 'c' && modifiers.Control:
element.clipboardPut(element.dot.Slice(element.text))
case key == 'v' && modifiers.Control:
window := element.entity.Window()
if window == nil { break }
window.Paste (func (d data.Data, err error) {
if err != nil { return }
reader, ok := d[data.MimePlain]
if !ok { return }
bytes, _ := io.ReadAll(reader)
element.text, element.dot = textmanip.Type (
element.text,
element.dot,
[]rune(string(bytes))...)
element.notifyAsyncTextChange()
})
case key == input.KeyMenu:
pos := fixedutil.RoundPt(element.valueDrawer.PositionAt(element.dot.End)).
Add(element.textOffset())
pos.Y += element.valueDrawer.LineHeight().Round()
element.contextMenu(pos)
case key.Printable():
element.text, element.dot = textmanip.Type (
@ -321,29 +311,54 @@ func (element *TextBox) HandleKeyDown(key input.Key, modifiers input.Modifiers)
element.dot,
rune(key))
textChanged = true
default:
altered = false
}
if textChanged {
element.runOnChange()
element.valueDrawer.SetText(element.text)
}
if altered {
element.scrollToCursor()
element.entity.Invalidate()
}
if (textChanged || scrollMemory != element.scroll) {
element.entity.NotifyScrollBoundsChange()
}
if altered {
element.entity.Invalidate()
}
// Cut cuts the selected text in the text box and places it in the clipboard.
func (element *TextBox) Cut () {
var lifted []rune
element.text, element.dot, lifted = textmanip.Lift (
element.text,
element.dot)
if lifted != nil {
element.clipboardPut(lifted)
element.notifyAsyncTextChange()
}
}
// Copy copies the selected text in the text box and places it in the clipboard.
func (element *TextBox) Copy () {
element.clipboardPut(element.dot.Slice(element.text))
}
// Paste pastes text data from the clipboard into the text box.
func (element *TextBox) Paste () {
window := element.entity.Window()
if window == nil { return }
window.Paste (func (d data.Data, err error) {
if err != nil { return }
reader, ok := d[data.MimePlain]
if !ok { return }
bytes, _ := io.ReadAll(reader)
element.text, element.dot = textmanip.Type (
element.text,
element.dot,
[]rune(string(bytes))...)
element.notifyAsyncTextChange()
})
}
func (element *TextBox) HandleKeyUp(key input.Key, modifiers input.Modifiers) { }
// SetPlaceholder sets the element's placeholder text.
@ -482,6 +497,42 @@ func (element *TextBox) SetConfig (new tomo.Config) {
element.entity.Invalidate()
}
func (element *TextBox) contextMenu (position image.Point) {
window := element.entity.Window()
menu, err := window.NewMenu(image.Rectangle { position, position })
if err != nil { return }
closeAnd := func (callback func ()) func () {
return func () { callback(); menu.Close() }
}
cutButton := NewButton("Cut")
cutButton.ShowText(false)
cutButton.SetIcon(tomo.IconCut)
cutButton.SetEnabled(!element.dot.Empty())
cutButton.OnClick(closeAnd(element.Cut))
copyButton := NewButton("Copy")
copyButton.ShowText(false)
copyButton.SetIcon(tomo.IconCopy)
copyButton.SetEnabled(!element.dot.Empty())
copyButton.OnClick(closeAnd(element.Copy))
pasteButton := NewButton("Paste")
pasteButton.ShowText(false)
pasteButton.SetIcon(tomo.IconPaste)
pasteButton.OnClick(closeAnd(element.Paste))
menu.Adopt (NewHBox (
SpaceNone,
pasteButton,
copyButton,
cutButton,
))
pasteButton.Focus()
menu.Show()
}
func (element *TextBox) runOnChange () {
if element.onChange != nil {
element.onChange()