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

View File

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

View File

@ -1,7 +1,10 @@
package elements package elements
import "image"
import "golang.org/x/image/math/fixed" import "golang.org/x/image/math/fixed"
import "git.tebibyte.media/sashakoshka/tomo" 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/canvas"
import "git.tebibyte.media/sashakoshka/tomo/textdraw" import "git.tebibyte.media/sashakoshka/tomo/textdraw"
import "git.tebibyte.media/sashakoshka/tomo/default/theme" import "git.tebibyte.media/sashakoshka/tomo/default/theme"
@ -48,6 +51,32 @@ func (element *Label) Entity () tomo.Entity {
return element.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 // 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 // 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. // 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() element.entity.Invalidate()
} }
// Draw causes the element to draw to the specified destination canvas. func (element *Label) HandleMouseDown (
func (element *Label) Draw (destination canvas.Canvas) { position image.Point,
bounds := element.entity.Bounds() button input.Button,
modifiers input.Modifiers,
if element.wrap { ) {
element.drawer.SetMaxWidth(bounds.Dx()) if button == input.ButtonRight {
element.drawer.SetMaxHeight(bounds.Dy()) 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() menu.Adopt (NewHBox (
foreground := element.theme.Color ( SpaceNone,
tomo.ColorForeground, copyButton,
tomo.State { }) ))
element.drawer.Draw(destination, foreground, bounds.Min.Sub(textBounds.Min)) copyButton.Focus()
menu.Show()
} }
func (element *Label) updateMinimumSize () { func (element *Label) updateMinimumSize () {

View File

@ -151,7 +151,8 @@ func (element *TextBox) HandleMouseDown (
if !element.Enabled() { return } if !element.Enabled() { return }
element.Focus() element.Focus()
if button == input.ButtonLeft { switch button {
case input.ButtonLeft:
runeIndex := element.atPosition(position) runeIndex := element.atPosition(position)
if runeIndex == -1 { return } if runeIndex == -1 { return }
@ -165,6 +166,18 @@ func (element *TextBox) HandleMouseDown (
} }
element.entity.Invalidate() 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))) 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) { func (element *TextBox) HandleKeyDown(key input.Key, modifiers input.Modifiers) {
if element.onKeyDown != nil && element.onKeyDown(key, modifiers) { if element.onKeyDown != nil && element.onKeyDown(key, modifiers) {
return return
} }
scrollMemory := element.scroll scrollMemory := element.scroll
altered := true
textChanged := false textChanged := false
switch { switch {
case key == input.KeyEnter: case key == input.KeyEnter:
@ -269,6 +271,8 @@ func (element *TextBox) HandleKeyDown(key input.Key, modifiers input.Modifiers)
element.dot, element.dot,
modifiers.Control) modifiers.Control)
} }
element.scrollToCursor()
element.entity.Invalidate()
case key == input.KeyRight: case key == input.KeyRight:
if modifiers.Shift { if modifiers.Shift {
@ -282,38 +286,24 @@ func (element *TextBox) HandleKeyDown(key input.Key, modifiers input.Modifiers)
element.dot, element.dot,
modifiers.Control) modifiers.Control)
} }
element.scrollToCursor()
element.entity.Invalidate()
case key == 'a' && modifiers.Control: case key == 'a' && modifiers.Control:
element.dot.Start = 0 element.dot.Start = 0
element.dot.End = len(element.text) element.dot.End = len(element.text)
element.scrollToCursor()
element.entity.Invalidate()
case key == 'x' && modifiers.Control: case key == 'x' && modifiers.Control: element.Cut()
var lifted []rune case key == 'c' && modifiers.Control: element.Copy()
element.text, element.dot, lifted = textmanip.Lift ( case key == 'v' && modifiers.Control: element.Paste()
element.text,
element.dot)
if lifted != nil {
element.clipboardPut(lifted)
textChanged = true
}
case key == 'c' && modifiers.Control: case key == input.KeyMenu:
element.clipboardPut(element.dot.Slice(element.text)) pos := fixedutil.RoundPt(element.valueDrawer.PositionAt(element.dot.End)).
Add(element.textOffset())
case key == 'v' && modifiers.Control: pos.Y += element.valueDrawer.LineHeight().Round()
window := element.entity.Window() element.contextMenu(pos)
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.Printable(): case key.Printable():
element.text, element.dot = textmanip.Type ( element.text, element.dot = textmanip.Type (
@ -321,29 +311,54 @@ func (element *TextBox) HandleKeyDown(key input.Key, modifiers input.Modifiers)
element.dot, element.dot,
rune(key)) rune(key))
textChanged = true textChanged = true
default:
altered = false
} }
if textChanged { if textChanged {
element.runOnChange() element.runOnChange()
element.valueDrawer.SetText(element.text) element.valueDrawer.SetText(element.text)
}
if altered {
element.scrollToCursor() element.scrollToCursor()
element.entity.Invalidate()
} }
if (textChanged || scrollMemory != element.scroll) { if (textChanged || scrollMemory != element.scroll) {
element.entity.NotifyScrollBoundsChange() 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) { } func (element *TextBox) HandleKeyUp(key input.Key, modifiers input.Modifiers) { }
// SetPlaceholder sets the element's placeholder text. // SetPlaceholder sets the element's placeholder text.
@ -482,6 +497,42 @@ func (element *TextBox) SetConfig (new tomo.Config) {
element.entity.Invalidate() 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 () { func (element *TextBox) runOnChange () {
if element.onChange != nil { if element.onChange != nil {
element.onChange() element.onChange()