From 537d69b491e07a315f1fa3323544a9ed39c4ba7b Mon Sep 17 00:00:00 2001 From: Sasha Koshka Date: Tue, 31 Jan 2023 14:29:35 -0500 Subject: [PATCH 01/10] Removed Resize, added DrawTo --- element.go | 7 ++++--- elements/core/core.go | 41 ++++++++++++++++------------------------- 2 files changed, 20 insertions(+), 28 deletions(-) diff --git a/element.go b/element.go index 192e71f..4a650d7 100644 --- a/element.go +++ b/element.go @@ -15,9 +15,10 @@ type Element interface { // instead of the offending dimension(s). MinimumSize () (width, height int) - // Resize resizes the element. This should only be called by the - // element's parent. - Resize (width, height int) + // DrawTo sets this element's canvas. This should only be called by the + // parent element. This is typically a region of the parent element's + // canvas. + DrawTo (canvas Canvas) // OnDamage sets a function to be called when an area of the element is // drawn on and should be pushed to the screen. diff --git a/elements/core/core.go b/elements/core/core.go index a3d0c68..faa2194 100644 --- a/elements/core/core.go +++ b/elements/core/core.go @@ -7,21 +7,21 @@ import "git.tebibyte.media/sashakoshka/tomo" // Core is a struct that implements some core functionality common to most // widgets. It is meant to be embedded directly into a struct. type Core struct { - canvas tomo.BasicCanvas - parent tomo.Element + canvas tomo.Canvas metrics struct { minimumWidth int minimumHeight int } + drawSizeChange func () onMinimumSizeChange func () onDamage func (region tomo.Canvas) } // NewCore creates a new element core and its corresponding control. -func NewCore (parent tomo.Element) (core *Core, control CoreControl) { - core = &Core { parent: parent } +func NewCore (drawSizeChange func ()) (core *Core, control CoreControl) { + core = &Core { drawSizeChange: drawSizeChange } control = CoreControl { core: core } return } @@ -33,21 +33,25 @@ func (core *Core) ColorModel () (model color.Model) { // ColorModel fulfills the draw.Image interface. func (core *Core) At (x, y int) (pixel color.Color) { + if core.canvas == nil { return } return core.canvas.At(x, y) } // ColorModel fulfills the draw.Image interface. func (core *Core) Bounds () (bounds image.Rectangle) { + if core.canvas == nil { return } return core.canvas.Bounds() } // ColorModel fulfills the draw.Image interface. func (core *Core) Set (x, y int, c color.Color) () { + if core.canvas == nil { return } core.canvas.Set(x, y, c) } // Buffer fulfills the tomo.Canvas interface. func (core *Core) Buffer () (data []color.RGBA, stride int) { + if core.canvas == nil { return } return core.canvas.Buffer() } @@ -57,6 +61,12 @@ func (core *Core) MinimumSize () (width, height int) { return core.metrics.minimumWidth, core.metrics.minimumHeight } +// DrawTo fulfills the tomo.Element interface. This should not need to be +// overridden. +func (core *Core) DrawTo (canvas tomo.Canvas) { + core.canvas = canvas +} + // OnDamage fulfils the tomo.Element interface. This should not need to be // overridden. func (core *Core) OnDamage (callback func (region tomo.Canvas)) { @@ -81,7 +91,7 @@ type CoreControl struct { // HasImage returns true if the core has an allocated image buffer, and false if // it doesn't. func (control CoreControl) HasImage () (has bool) { - return !control.Bounds().Empty() + return control.core.canvas != nil } // DamageRegion pushes the selected region of pixels to the parent element. This @@ -93,18 +103,11 @@ func (control CoreControl) DamageRegion (bounds image.Rectangle) { } // DamageAll pushes all pixels to the parent element. This does not need to be -// called when responding to a resize event. +// called when redrawing in response to a change in size. func (control CoreControl) DamageAll () { control.DamageRegion(control.Bounds()) } -// AllocateCanvas resizes the canvas, constraining the width and height so that -// they are not less than the specified minimum width and height. -func (control *CoreControl) AllocateCanvas (width, height int) { - control.core.canvas = tomo.NewBasicCanvas(width, height) - control.BasicCanvas = control.core.canvas -} - // SetMinimumSize sets the minimum size of this element, notifying the parent // element in the process. func (control CoreControl) SetMinimumSize (width, height int) { @@ -119,18 +122,6 @@ func (control CoreControl) SetMinimumSize (width, height int) { if control.core.onMinimumSizeChange != nil { control.core.onMinimumSizeChange() } - - // if there is an image buffer, and the current size is less - // than this new minimum size, send core.parent a resize event. - if control.HasImage() { - bounds := control.Bounds() - imageWidth, - imageHeight, - constrained := control.ConstrainSize(bounds.Dx(), bounds.Dy()) - if constrained { - core.parent.Resize(imageWidth, imageHeight) - } - } } // ConstrainSize contstrains the specified width and height to the minimum width -- 2.30.2 From 81fc82c46e179666a1cb42757e76bb5d1a5ed9b8 Mon Sep 17 00:00:00 2001 From: Sasha Koshka Date: Tue, 31 Jan 2023 14:48:28 -0500 Subject: [PATCH 02/10] Layouts no longer resize elements (because they cant) --- layout.go | 2 +- layouts/dialog.go | 21 +++++++++++---------- layouts/horizontal.go | 7 ++++--- layouts/vertical.go | 7 ++++--- 4 files changed, 20 insertions(+), 17 deletions(-) diff --git a/layout.go b/layout.go index 8ca1f8c..e107cba 100644 --- a/layout.go +++ b/layout.go @@ -6,7 +6,7 @@ import "image" // it can be arranged by a Layout. type LayoutEntry struct { Element - Position image.Point + Bounds image.Rectangle Expand bool } diff --git a/layouts/dialog.go b/layouts/dialog.go index a9c2f90..224dad4 100644 --- a/layouts/dialog.go +++ b/layouts/dialog.go @@ -32,19 +32,19 @@ func (layout Dialog) Arrange (entries []tomo.LayoutEntry, width, height int) { } if len(entries) > 0 { - entries[0].Position = image.Point { } + entries[0].Bounds.Min = image.Point { } if layout.Pad { - entries[0].Position.X += theme.Margin() - entries[0].Position.Y += theme.Margin() + entries[0].Bounds.Min.X += theme.Margin() + entries[0].Bounds.Min.Y += theme.Margin() } mainHeight := height - controlRowHeight if layout.Gap { mainHeight -= theme.Margin() } - mainBounds := entries[0].Bounds() - if mainBounds.Dy() != mainHeight || - mainBounds.Dx() != width { - entries[0].Resize(width, mainHeight) + mainBounds := entries[0].Bounds + if mainBounds.Dy() != mainHeight || mainBounds.Dx() != width { + entries[0].Bounds.Max = + mainBounds.Min.Add(image.Pt(width, mainHeight)) } } @@ -85,7 +85,7 @@ func (layout Dialog) Arrange (entries []tomo.LayoutEntry, width, height int) { for index, entry := range entries[1:] { if index > 0 && layout.Gap { x += theme.Margin() } - entries[index + 1].Position = image.Pt(x, y) + entries[index + 1].Bounds.Min = image.Pt(x, y) entryWidth := 0 if entry.Expand { entryWidth = expandingElementWidth @@ -93,10 +93,11 @@ func (layout Dialog) Arrange (entries []tomo.LayoutEntry, width, height int) { entryWidth, _ = entry.MinimumSize() } x += entryWidth - entryBounds := entry.Bounds() + entryBounds := entry.Bounds if entryBounds.Dy() != controlRowHeight || entryBounds.Dx() != entryWidth { - entry.Resize(entryWidth, controlRowHeight) + entry.Bounds.Max = entryBounds.Min.Add ( + image.Pt(entryWidth, controlRowHeight)) } } } diff --git a/layouts/horizontal.go b/layouts/horizontal.go index abe18db..6fd0743 100644 --- a/layouts/horizontal.go +++ b/layouts/horizontal.go @@ -35,7 +35,7 @@ func (layout Horizontal) Arrange (entries []tomo.LayoutEntry, width, height int) for index, entry := range entries { if index > 0 && layout.Gap { x += theme.Margin() } - entries[index].Position = image.Pt(x, y) + entries[index].Bounds.Min = image.Pt(x, y) entryWidth := 0 if entry.Expand { entryWidth = expandingElementWidth @@ -43,9 +43,10 @@ func (layout Horizontal) Arrange (entries []tomo.LayoutEntry, width, height int) entryWidth, _ = entry.MinimumSize() } x += entryWidth - entryBounds := entry.Bounds() + entryBounds := entry.Bounds if entryBounds.Dy() != height || entryBounds.Dx() != entryWidth { - entry.Resize(entryWidth, height) + entry.Bounds.Max = entryBounds.Min.Add ( + image.Pt(entryWidth, height)) } } } diff --git a/layouts/vertical.go b/layouts/vertical.go index ea43752..365e4b3 100644 --- a/layouts/vertical.go +++ b/layouts/vertical.go @@ -62,7 +62,7 @@ func (layout Vertical) Arrange (entries []tomo.LayoutEntry, width, height int) { for index, entry := range entries { if index > 0 && layout.Gap { y += theme.Margin() } - entries[index].Position = image.Pt(x, y) + entries[index].Bounds.Min = image.Pt(x, y) entryHeight := 0 if entry.Expand { entryHeight = expandingElementHeight @@ -70,9 +70,10 @@ func (layout Vertical) Arrange (entries []tomo.LayoutEntry, width, height int) { entryHeight = minimumHeights[index] } y += entryHeight - entryBounds := entry.Bounds() + entryBounds := entry.Bounds if entryBounds.Dx() != width || entryBounds.Dy() != entryHeight { - entry.Resize(width, entryHeight) + entry.Bounds.Max = entryBounds.Min.Add ( + image.Pt(width, entryHeight)) } } } -- 2.30.2 From ee424b91256a4508aa7d794517ef31915318cd25 Mon Sep 17 00:00:00 2001 From: Sasha Koshka Date: Tue, 31 Jan 2023 14:54:43 -0500 Subject: [PATCH 03/10] Basic elements conform to new API --- elements/basic/button.go | 7 +--- elements/basic/checkbox.go | 8 +--- elements/basic/container.go | 66 +++++++++++-------------------- elements/basic/label.go | 11 +++--- elements/basic/list.go | 7 +--- elements/basic/progressbar.go | 9 +---- elements/basic/scrollcontainer.go | 16 +++----- elements/basic/spacer.go | 9 +---- elements/basic/switch.go | 8 +--- elements/basic/textbox.go | 5 +-- 10 files changed, 43 insertions(+), 103 deletions(-) diff --git a/elements/basic/button.go b/elements/basic/button.go index 62e39c2..55e0c15 100644 --- a/elements/basic/button.go +++ b/elements/basic/button.go @@ -25,7 +25,7 @@ type Button struct { // NewButton creates a new button with the specified label text. func NewButton (text string) (element *Button) { element = &Button { } - element.Core, element.core = core.NewCore(element) + element.Core, element.core = core.NewCore(element.draw) element.FocusableCore, element.focusableControl = core.NewFocusableCore (func () { if element.core.HasImage () { @@ -38,11 +38,6 @@ func NewButton (text string) (element *Button) { return } -func (element *Button) Resize (width, height int) { - element.core.AllocateCanvas(width, height) - element.draw() -} - func (element *Button) HandleMouseDown (x, y int, button tomo.Button) { if !element.Enabled() { return } if !element.Focused() { element.Focus() } diff --git a/elements/basic/checkbox.go b/elements/basic/checkbox.go index 115b79a..c3bbae9 100644 --- a/elements/basic/checkbox.go +++ b/elements/basic/checkbox.go @@ -26,7 +26,7 @@ type Checkbox struct { // NewCheckbox creates a new cbeckbox with the specified label text. func NewCheckbox (text string, checked bool) (element *Checkbox) { element = &Checkbox { checked: checked } - element.Core, element.core = core.NewCore(element) + element.Core, element.core = core.NewCore(element.draw) element.FocusableCore, element.focusableControl = core.NewFocusableCore (func () { if element.core.HasImage () { @@ -39,12 +39,6 @@ func NewCheckbox (text string, checked bool) (element *Checkbox) { return } -// Resize changes this element's size. -func (element *Checkbox) Resize (width, height int) { - element.core.AllocateCanvas(width, height) - element.draw() -} - func (element *Checkbox) HandleMouseDown (x, y int, button tomo.Button) { if !element.Enabled() { return } element.Focus() diff --git a/elements/basic/container.go b/elements/basic/container.go index a223e70..822f282 100644 --- a/elements/basic/container.go +++ b/elements/basic/container.go @@ -30,7 +30,7 @@ type Container struct { // NewContainer creates a new container. func NewContainer (layout tomo.Layout) (element *Container) { element = &Container { } - element.Core, element.core = core.NewCore(element) + element.Core, element.core = core.NewCore(element.redoAll) element.SetLayout(layout) return } @@ -39,8 +39,7 @@ func NewContainer (layout tomo.Layout) (element *Container) { func (element *Container) SetLayout (layout tomo.Layout) { element.layout = layout if element.core.HasImage() { - element.recalculate() - element.draw() + element.redoAll() element.core.DamageAll() } } @@ -51,7 +50,7 @@ func (element *Container) SetLayout (layout tomo.Layout) { func (element *Container) Adopt (child tomo.Element, expand bool) { // set event handlers child.OnDamage (func (region tomo.Canvas) { - element.drawChildRegion(child, region) + element.core.DamageRegion(region.Bounds()) }) child.OnMinimumSizeChange(element.updateMinimumSize) if child0, ok := child.(tomo.Flexible); ok { @@ -78,8 +77,7 @@ func (element *Container) Adopt (child tomo.Element, expand bool) { element.updateMinimumSize() element.reflectChildProperties() if element.core.HasImage() && !element.warping { - element.recalculate() - element.draw() + element.redoAll() element.core.DamageAll() } } @@ -101,8 +99,7 @@ func (element *Container) Warp (callback func ()) { // and redraw every time, because although that is the most likely use // case, it is not the only one. if element.core.HasImage() { - element.recalculate() - element.draw() + element.redoAll() element.core.DamageAll() } } @@ -123,13 +120,13 @@ func (element *Container) Disown (child tomo.Element) { element.updateMinimumSize() element.reflectChildProperties() if element.core.HasImage() && !element.warping { - element.recalculate() - element.draw() + element.redoAll() element.core.DamageAll() } } func (element *Container) clearChildEventHandlers (child tomo.Element) { + child.DrawTo(nil) child.OnDamage(nil) child.OnMinimumSizeChange(nil) if child0, ok := child.(tomo.Focusable); ok { @@ -151,8 +148,7 @@ func (element *Container) DisownAll () { element.updateMinimumSize() element.reflectChildProperties() if element.core.HasImage() && !element.warping { - element.recalculate() - element.draw() + element.redoAll() element.core.DamageAll() } } @@ -182,7 +178,7 @@ func (element *Container) Child (index int) (child tomo.Element) { // there are no children at the coordinates, this method will return nil. func (element *Container) ChildAt (point image.Point) (child tomo.Element) { for _, entry := range element.children { - if point.In(entry.Bounds().Add(entry.Position)) { + if point.In(entry.Bounds) { child = entry.Element } } @@ -192,7 +188,7 @@ func (element *Container) ChildAt (point image.Point) (child tomo.Element) { func (element *Container) childPosition (child tomo.Element) (position image.Point) { for _, entry := range element.children { if entry.Element == child { - position = entry.Position + position = entry.Bounds.Min break } } @@ -200,10 +196,21 @@ func (element *Container) childPosition (child tomo.Element) (position image.Poi return } -func (element *Container) Resize (width, height int) { - element.core.AllocateCanvas(width, height) +func (element *Container) redoAll () { + // do a layout element.recalculate() - element.draw() + + // draw a background + bounds := element.core.Bounds() + pattern, _ := theme.BackgroundPattern (theme.PatternState { + Case: containerCase, + }) + artist.FillRectangle(element.core, pattern, bounds) + + // resize all elements, having them draw onto us + for _, entry := range element.children { + entry.DrawTo(tomo.Cut(element, entry.Bounds)) + } } func (element *Container) HandleMouseDown (x, y int, button tomo.Button) { @@ -471,28 +478,3 @@ func (element *Container) recalculate () { bounds := element.Bounds() element.layout.Arrange(element.children, bounds.Dx(), bounds.Dy()) } - -func (element *Container) draw () { - bounds := element.core.Bounds() - - pattern, _ := theme.BackgroundPattern (theme.PatternState { - Case: containerCase, - }) - artist.FillRectangle(element.core, pattern, bounds) - - for _, entry := range element.children { - artist.Paste(element.core, entry, entry.Position) - } -} - -func (element *Container) drawChildRegion (child tomo.Element, region tomo.Canvas) { - if element.warping { return } - for _, entry := range element.children { - if entry.Element == child { - artist.Paste(element.core, region, entry.Position) - element.core.DamageRegion ( - region.Bounds().Add(entry.Position)) - break - } - } -} diff --git a/elements/basic/label.go b/elements/basic/label.go index d5a9e5d..70c5a21 100644 --- a/elements/basic/label.go +++ b/elements/basic/label.go @@ -23,7 +23,7 @@ type Label struct { // wrapped. func NewLabel (text string, wrap bool) (element *Label) { element = &Label { } - element.Core, element.core = core.NewCore(element) + element.Core, element.core = core.NewCore(element.handleResize) face := theme.FontFaceRegular() element.drawer.SetFace(face) element.SetWrap(wrap) @@ -31,12 +31,11 @@ func NewLabel (text string, wrap bool) (element *Label) { return } -// Resize resizes the label and re-wraps the text if wrapping is enabled. -func (element *Label) Resize (width, height int) { - element.core.AllocateCanvas(width, height) +func (element *Label) handleResize () { + bounds := element.Bounds() if element.wrap { - element.drawer.SetMaxWidth(width) - element.drawer.SetMaxHeight(height) + element.drawer.SetMaxWidth(bounds.Dx()) + element.drawer.SetMaxHeight(bounds.Dy()) } element.draw() return diff --git a/elements/basic/list.go b/elements/basic/list.go index a871c94..aebdd67 100644 --- a/elements/basic/list.go +++ b/elements/basic/list.go @@ -33,7 +33,7 @@ type List struct { // NewList creates a new list element with the specified entries. func NewList (entries ...ListEntry) (element *List) { element = &List { selectedEntry: -1 } - element.Core, element.core = core.NewCore(element) + element.Core, element.core = core.NewCore(element.handleResize) element.FocusableCore, element.focusableControl = core.NewFocusableCore (func () { if element.core.HasImage () { @@ -51,10 +51,7 @@ func NewList (entries ...ListEntry) (element *List) { return } -// Resize changes the element's size. -func (element *List) Resize (width, height int) { - element.core.AllocateCanvas(width, height) - +func (element *List) handleResize () { for index, entry := range element.entries { element.entries[index] = element.resizeEntryToFit(entry) } diff --git a/elements/basic/progressbar.go b/elements/basic/progressbar.go index 717320d..24e3307 100644 --- a/elements/basic/progressbar.go +++ b/elements/basic/progressbar.go @@ -16,18 +16,11 @@ type ProgressBar struct { // level. func NewProgressBar (progress float64) (element *ProgressBar) { element = &ProgressBar { progress: progress } - element.Core, element.core = core.NewCore(element) + element.Core, element.core = core.NewCore(element.draw) element.core.SetMinimumSize(theme.Padding() * 2, theme.Padding() * 2) return } -// Resize resizes the progress bar. -func (element *ProgressBar) Resize (width, height int) { - element.core.AllocateCanvas(width, height) - element.draw() - return -} - // SetProgress sets the progress level of the bar. func (element *ProgressBar) SetProgress (progress float64) { if progress == element.progress { return } diff --git a/elements/basic/scrollcontainer.go b/elements/basic/scrollcontainer.go index ad18cf5..69d8250 100644 --- a/elements/basic/scrollcontainer.go +++ b/elements/basic/scrollcontainer.go @@ -48,20 +48,16 @@ type ScrollContainer struct { // bars. func NewScrollContainer (horizontal, vertical bool) (element *ScrollContainer) { element = &ScrollContainer { } - element.Core, element.core = core.NewCore(element) + element.Core, element.core = core.NewCore(element.handleResize) element.updateMinimumSize() element.horizontal.exists = horizontal element.vertical.exists = vertical return } -// Resize resizes the scroll box. -func (element *ScrollContainer) Resize (width, height int) { - element.core.AllocateCanvas(width, height) +func (element *ScrollContainer) handleResize () { element.recalculate() - element.child.Resize ( - element.childWidth, - element.childHeight) + element.child.DrawTo(tomo.Cut(element, element.child.Bounds())) element.draw() } @@ -95,10 +91,7 @@ func (element *ScrollContainer) Adopt (child tomo.Scrollable) { element.vertical.enabled = element.child.ScrollAxes() if element.core.HasImage() { - element.child.Resize ( - element.childWidth, - element.childHeight) - element.core.DamageAll() + element.child.DrawTo(tomo.Cut(element, element.child.Bounds())) } } } @@ -259,6 +252,7 @@ func (element *ScrollContainer) childFocusMotionRequestCallback ( } func (element *ScrollContainer) clearChildEventHandlers (child tomo.Scrollable) { + child.DrawTo(nil) child.OnDamage(nil) child.OnMinimumSizeChange(nil) child.OnScrollBoundsChange(nil) diff --git a/elements/basic/spacer.go b/elements/basic/spacer.go index 31883e5..bf56979 100644 --- a/elements/basic/spacer.go +++ b/elements/basic/spacer.go @@ -18,18 +18,11 @@ type Spacer struct { // will appear as a line. func NewSpacer (line bool) (element *Spacer) { element = &Spacer { line: line } - element.Core, element.core = core.NewCore(element) + element.Core, element.core = core.NewCore(element.draw) element.core.SetMinimumSize(1, 1) return } -// Resize resizes the label and re-wraps the text if wrapping is enabled. -func (element *Spacer) Resize (width, height int) { - element.core.AllocateCanvas(width, height) - element.draw() - return -} - /// SetLine sets whether or not the spacer will appear as a colored line. func (element *Spacer) SetLine (line bool) { if element.line == line { return } diff --git a/elements/basic/switch.go b/elements/basic/switch.go index 9fdf1b2..85610ba 100644 --- a/elements/basic/switch.go +++ b/elements/basic/switch.go @@ -27,7 +27,7 @@ type Switch struct { // NewSwitch creates a new switch with the specified label text. func NewSwitch (text string, on bool) (element *Switch) { element = &Switch { checked: on, text: text } - element.Core, element.core = core.NewCore(element) + element.Core, element.core = core.NewCore(element.draw) element.FocusableCore, element.focusableControl = core.NewFocusableCore (func () { if element.core.HasImage () { @@ -41,12 +41,6 @@ func NewSwitch (text string, on bool) (element *Switch) { return } -// Resize changes this element's size. -func (element *Switch) Resize (width, height int) { - element.core.AllocateCanvas(width, height) - element.draw() -} - func (element *Switch) HandleMouseDown (x, y int, button tomo.Button) { if !element.Enabled() { return } element.Focus() diff --git a/elements/basic/textbox.go b/elements/basic/textbox.go index 703f504..fab0bb8 100644 --- a/elements/basic/textbox.go +++ b/elements/basic/textbox.go @@ -34,7 +34,7 @@ type TextBox struct { // text. func NewTextBox (placeholder, value string) (element *TextBox) { element = &TextBox { } - element.Core, element.core = core.NewCore(element) + element.Core, element.core = core.NewCore(element.handleResize) element.FocusableCore, element.focusableControl = core.NewFocusableCore (func () { if element.core.HasImage () { @@ -51,8 +51,7 @@ func NewTextBox (placeholder, value string) (element *TextBox) { return } -func (element *TextBox) Resize (width, height int) { - element.core.AllocateCanvas(width, height) +func (element *TextBox) handleResize () { element.scrollToCursor() element.draw() if element.onScrollBoundsChange != nil { -- 2.30.2 From d5028317ef37271f47b7d3c3322d869f75e24f1e Mon Sep 17 00:00:00 2001 From: Sasha Koshka Date: Tue, 31 Jan 2023 14:57:02 -0500 Subject: [PATCH 04/10] Fun and testing elements conform to new API --- elements/fun/clock.go | 8 +------- elements/testing/artist.go | 5 ++--- elements/testing/mouse.go | 5 ++--- 3 files changed, 5 insertions(+), 13 deletions(-) diff --git a/elements/fun/clock.go b/elements/fun/clock.go index 8bc71f7..1e98029 100644 --- a/elements/fun/clock.go +++ b/elements/fun/clock.go @@ -19,17 +19,11 @@ type AnalogClock struct { // NewAnalogClock creates a new analog clock that displays the specified time. func NewAnalogClock (newTime time.Time) (element *AnalogClock) { element = &AnalogClock { } - element.Core, element.core = core.NewCore(element) + element.Core, element.core = core.NewCore(element.draw) element.core.SetMinimumSize(64, 64) return } -// Resize changes the size of the clock. -func (element *AnalogClock) Resize (width, height int) { - element.core.AllocateCanvas(width, height) - element.draw() -} - // SetTime changes the time that the clock displays. func (element *AnalogClock) SetTime (newTime time.Time) { if newTime == element.time { return } diff --git a/elements/testing/artist.go b/elements/testing/artist.go index 628cd56..145c629 100644 --- a/elements/testing/artist.go +++ b/elements/testing/artist.go @@ -19,13 +19,12 @@ type Artist struct { // NewArtist creates a new artist test element. func NewArtist () (element *Artist) { element = &Artist { } - element.Core, element.core = core.NewCore(element) + element.Core, element.core = core.NewCore(element.draw) element.core.SetMinimumSize(480, 600) return } -func (element *Artist) Resize (width, height int) { - element.core.AllocateCanvas(width, height) +func (element *Artist) draw () { bounds := element.Bounds() element.cellBounds.Max.X = bounds.Dx() / 5 element.cellBounds.Max.Y = (bounds.Dy() - 48) / 8 diff --git a/elements/testing/mouse.go b/elements/testing/mouse.go index 5d3fae8..138238a 100644 --- a/elements/testing/mouse.go +++ b/elements/testing/mouse.go @@ -20,14 +20,13 @@ type Mouse struct { // NewMouse creates a new mouse test element. func NewMouse () (element *Mouse) { element = &Mouse { } - element.Core, element.core = core.NewCore(element) + element.Core, element.core = core.NewCore(element.draw) element.core.SetMinimumSize(32, 32) element.color = artist.NewUniform(color.Black) return } -func (element *Mouse) Resize (width, height int) { - element.core.AllocateCanvas(width, height) +func (element *Mouse) draw () { bounds := element.Bounds() pattern, _ := theme.AccentPattern(theme.PatternState { }) artist.FillRectangle(element.core, pattern, bounds) -- 2.30.2 From 6d6a0c59a19e159779b33fc56942264a2c0642fc Mon Sep 17 00:00:00 2001 From: Sasha Koshka Date: Tue, 31 Jan 2023 16:13:20 -0500 Subject: [PATCH 05/10] X backend now conforms to new API --- backends/x/window.go | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/backends/x/window.go b/backends/x/window.go index 30ef7ce..8215b0d 100644 --- a/backends/x/window.go +++ b/backends/x/window.go @@ -13,6 +13,7 @@ type Window struct { backend *Backend xWindow *xwindow.Window xCanvas *xgraphics.Image + canvas tomo.BasicCanvas child tomo.Element onClose func () skipChildDrawCallback bool @@ -194,6 +195,9 @@ func (window *Window) OnClose (callback func ()) { } func (window *Window) reallocateCanvas () { + window.canvas = tomo.NewBasicCanvas ( + window.metrics.width, + window.metrics.height) if window.xCanvas != nil { window.xCanvas.Destroy() } @@ -203,12 +207,12 @@ func (window *Window) reallocateCanvas () { 0, 0, window.metrics.width, window.metrics.height)) - window.xCanvas.CreatePixmap() } func (window *Window) redrawChildEntirely () { window.pushRegion(window.paste(window.child)) + } func (window *Window) resizeChildToFit () { @@ -228,15 +232,10 @@ func (window *Window) resizeChildToFit () { if window.metrics.height >= minimumHeight && window.metrics.width >= minimumWidth { - - window.child.Resize ( - window.metrics.width, - window.metrics.height) + window.child.DrawTo(window.canvas) } } else { - window.child.Resize ( - window.metrics.width, - window.metrics.height) + window.child.DrawTo(window.canvas) } window.skipChildDrawCallback = false } -- 2.30.2 From 2f9504b1e4061d8d292990cebd495214dd4f690d Mon Sep 17 00:00:00 2001 From: Sasha Koshka Date: Tue, 31 Jan 2023 16:18:10 -0500 Subject: [PATCH 06/10] Fixed wierd inconsistency with element core --- elements/basic/button.go | 6 +++--- elements/basic/checkbox.go | 10 +++++----- elements/basic/container.go | 4 ++-- elements/basic/label.go | 6 +++--- elements/basic/list.go | 2 +- elements/basic/progressbar.go | 6 +++--- elements/basic/scrollcontainer.go | 2 +- elements/basic/spacer.go | 6 +++--- elements/basic/switch.go | 10 +++++----- elements/basic/textbox.go | 12 ++++++------ elements/core/core.go | 10 ++++++---- elements/fun/clock.go | 6 +++--- elements/testing/mouse.go | 12 ++++++------ 13 files changed, 47 insertions(+), 45 deletions(-) diff --git a/elements/basic/button.go b/elements/basic/button.go index 55e0c15..f9a011f 100644 --- a/elements/basic/button.go +++ b/elements/basic/button.go @@ -121,7 +121,7 @@ func (element *Button) SetText (text string) { } func (element *Button) draw () { - bounds := element.core.Bounds() + bounds := element.Bounds() pattern, inset := theme.ButtonPattern(theme.PatternState { Case: buttonCase, @@ -130,7 +130,7 @@ func (element *Button) draw () { Pressed: element.pressed, }) - artist.FillRectangle(element.core, pattern, bounds) + artist.FillRectangle(element, pattern, bounds) innerBounds := inset.Apply(bounds) @@ -149,5 +149,5 @@ func (element *Button) draw () { Case: buttonCase, Disabled: !element.Enabled(), }) - element.drawer.Draw(element.core, foreground, offset) + element.drawer.Draw(element, foreground, offset) } diff --git a/elements/basic/checkbox.go b/elements/basic/checkbox.go index c3bbae9..839bf21 100644 --- a/elements/basic/checkbox.go +++ b/elements/basic/checkbox.go @@ -133,13 +133,13 @@ func (element *Checkbox) SetText (text string) { } func (element *Checkbox) draw () { - bounds := element.core.Bounds() + bounds := element.Bounds() boxBounds := image.Rect(0, 0, bounds.Dy(), bounds.Dy()) backgroundPattern, _ := theme.BackgroundPattern(theme.PatternState { Case: checkboxCase, }) - artist.FillRectangle ( element.core, backgroundPattern, bounds) + artist.FillRectangle(element, backgroundPattern, bounds) pattern, inset := theme.ButtonPattern(theme.PatternState { Case: checkboxCase, @@ -147,7 +147,7 @@ func (element *Checkbox) draw () { Focused: element.Focused(), Pressed: element.pressed, }) - artist.FillRectangle(element.core, pattern, boxBounds) + artist.FillRectangle(element, pattern, boxBounds) textBounds := element.drawer.LayoutBounds() offset := image.Point { @@ -161,10 +161,10 @@ func (element *Checkbox) draw () { Case: checkboxCase, Disabled: !element.Enabled(), }) - element.drawer.Draw(element.core, foreground, offset) + element.drawer.Draw(element, foreground, offset) if element.checked { checkBounds := inset.Apply(boxBounds).Inset(2) - artist.FillRectangle(element.core, foreground, checkBounds) + artist.FillRectangle(element, foreground, checkBounds) } } diff --git a/elements/basic/container.go b/elements/basic/container.go index 822f282..2d30952 100644 --- a/elements/basic/container.go +++ b/elements/basic/container.go @@ -201,11 +201,11 @@ func (element *Container) redoAll () { element.recalculate() // draw a background - bounds := element.core.Bounds() + bounds := element.Bounds() pattern, _ := theme.BackgroundPattern (theme.PatternState { Case: containerCase, }) - artist.FillRectangle(element.core, pattern, bounds) + artist.FillRectangle(element, pattern, bounds) // resize all elements, having them draw onto us for _, entry := range element.children { diff --git a/elements/basic/label.go b/elements/basic/label.go index 70c5a21..70a8581 100644 --- a/elements/basic/label.go +++ b/elements/basic/label.go @@ -107,19 +107,19 @@ func (element *Label) updateMinimumSize () { } func (element *Label) draw () { - bounds := element.core.Bounds() + bounds := element.Bounds() pattern, _ := theme.BackgroundPattern(theme.PatternState { Case: labelCase, }) - artist.FillRectangle(element.core, pattern, bounds) + artist.FillRectangle(element, pattern, bounds) textBounds := element.drawer.LayoutBounds() foreground, _ := theme.ForegroundPattern (theme.PatternState { Case: labelCase, }) - element.drawer.Draw (element.core, foreground, image.Point { + element.drawer.Draw (element, foreground, image.Point { X: 0 - textBounds.Min.X, Y: 0 - textBounds.Min.Y, }) diff --git a/elements/basic/list.go b/elements/basic/list.go index aebdd67..854aa93 100644 --- a/elements/basic/list.go +++ b/elements/basic/list.go @@ -376,7 +376,7 @@ func (element *List) draw () { Disabled: !element.Enabled(), Focused: element.Focused(), }) - artist.FillRectangle(element.core, pattern, bounds) + artist.FillRectangle(element, pattern, bounds) bounds = inset.Apply(bounds) dot := image.Point { diff --git a/elements/basic/progressbar.go b/elements/basic/progressbar.go index 24e3307..4344f2c 100644 --- a/elements/basic/progressbar.go +++ b/elements/basic/progressbar.go @@ -32,15 +32,15 @@ func (element *ProgressBar) SetProgress (progress float64) { } func (element *ProgressBar) draw () { - bounds := element.core.Bounds() + bounds := element.Bounds() pattern, inset := theme.SunkenPattern(theme.PatternState { }) - artist.FillRectangle(element.core, pattern, bounds) + artist.FillRectangle(element, pattern, bounds) bounds = inset.Apply(bounds) meterBounds := image.Rect ( bounds.Min.X, bounds.Min.Y, bounds.Min.X + int(float64(bounds.Dx()) * element.progress), bounds.Max.Y) accent, _ := theme.AccentPattern(theme.PatternState { }) - artist.FillRectangle(element.core, accent, meterBounds) + artist.FillRectangle(element, accent, meterBounds) } diff --git a/elements/basic/scrollcontainer.go b/elements/basic/scrollcontainer.go index 69d8250..f5cb8c3 100644 --- a/elements/basic/scrollcontainer.go +++ b/elements/basic/scrollcontainer.go @@ -358,7 +358,7 @@ func (element *ScrollContainer) recalculate () { } func (element *ScrollContainer) draw () { - artist.Paste(element.core, element.child, image.Point { }) + artist.Paste(element, element.child, image.Point { }) deadPattern, _ := theme.DeadPattern(theme.PatternState { Case: scrollContainerCase, }) diff --git a/elements/basic/spacer.go b/elements/basic/spacer.go index bf56979..940c462 100644 --- a/elements/basic/spacer.go +++ b/elements/basic/spacer.go @@ -34,19 +34,19 @@ func (element *Spacer) SetLine (line bool) { } func (element *Spacer) draw () { - bounds := element.core.Bounds() + bounds := element.Bounds() if element.line { pattern, _ := theme.ForegroundPattern(theme.PatternState { Case: spacerCase, Disabled: true, }) - artist.FillRectangle(element.core, pattern, bounds) + artist.FillRectangle(element, pattern, bounds) } else { pattern, _ := theme.BackgroundPattern(theme.PatternState { Case: spacerCase, Disabled: true, }) - artist.FillRectangle(element.core, pattern, bounds) + artist.FillRectangle(element, pattern, bounds) } } diff --git a/elements/basic/switch.go b/elements/basic/switch.go index 85610ba..d3ac782 100644 --- a/elements/basic/switch.go +++ b/elements/basic/switch.go @@ -140,13 +140,13 @@ func (element *Switch) calculateMinimumSize () { } func (element *Switch) draw () { - bounds := element.core.Bounds() + bounds := element.Bounds() handleBounds := image.Rect(0, 0, bounds.Dy(), bounds.Dy()) gutterBounds := image.Rect(0, 0, bounds.Dy() * 2, bounds.Dy()) backgroundPattern, _ := theme.BackgroundPattern(theme.PatternState { Case: switchCase, }) - artist.FillRectangle ( element.core, backgroundPattern, bounds) + artist.FillRectangle (element, backgroundPattern, bounds) if element.checked { handleBounds.Min.X += bounds.Dy() @@ -168,7 +168,7 @@ func (element *Switch) draw () { Focused: element.Focused(), Pressed: element.pressed, }) - artist.FillRectangle(element.core, gutterPattern, gutterBounds) + artist.FillRectangle(element, gutterPattern, gutterBounds) handlePattern, _ := theme.HandlePattern(theme.PatternState { Case: switchCase, @@ -176,7 +176,7 @@ func (element *Switch) draw () { Focused: element.Focused(), Pressed: element.pressed, }) - artist.FillRectangle(element.core, handlePattern, handleBounds) + artist.FillRectangle(element, handlePattern, handleBounds) textBounds := element.drawer.LayoutBounds() offset := image.Point { @@ -190,5 +190,5 @@ func (element *Switch) draw () { Case: switchCase, Disabled: !element.Enabled(), }) - element.drawer.Draw(element.core, foreground, offset) + element.drawer.Draw(element, foreground, offset) } diff --git a/elements/basic/textbox.go b/elements/basic/textbox.go index fab0bb8..67cccef 100644 --- a/elements/basic/textbox.go +++ b/elements/basic/textbox.go @@ -257,7 +257,7 @@ func (element *TextBox) runOnChange () { func (element *TextBox) scrollToCursor () { if !element.core.HasImage() { return } - bounds := element.core.Bounds().Inset(theme.Padding()) + bounds := element.Bounds().Inset(theme.Padding()) bounds.Max.X -= element.valueDrawer.Em().Round() cursorPosition := element.valueDrawer.PositionOf(element.cursor) cursorPosition.X -= element.scroll @@ -272,7 +272,7 @@ func (element *TextBox) scrollToCursor () { } func (element *TextBox) draw () { - bounds := element.core.Bounds() + bounds := element.Bounds() // FIXME: take index into account pattern, inset := theme.InputPattern(theme.PatternState { @@ -280,7 +280,7 @@ func (element *TextBox) draw () { Disabled: !element.Enabled(), Focused: element.Focused(), }) - artist.FillRectangle(element.core, pattern, bounds) + artist.FillRectangle(element, pattern, bounds) if len(element.text) == 0 && !element.Focused() { // draw placeholder @@ -294,7 +294,7 @@ func (element *TextBox) draw () { Disabled: true, }) element.placeholderDrawer.Draw ( - element.core, + element, foreground, offset.Sub(textBounds.Min)) } else { @@ -309,7 +309,7 @@ func (element *TextBox) draw () { Disabled: !element.Enabled(), }) element.valueDrawer.Draw ( - element.core, + element, foreground, offset.Sub(textBounds.Min)) @@ -321,7 +321,7 @@ func (element *TextBox) draw () { Case: textBoxCase, }) artist.Line ( - element.core, + element, foreground, 1, cursorPosition.Add(offset), image.Pt ( diff --git a/elements/core/core.go b/elements/core/core.go index faa2194..6a42623 100644 --- a/elements/core/core.go +++ b/elements/core/core.go @@ -65,6 +65,9 @@ func (core *Core) MinimumSize () (width, height int) { // overridden. func (core *Core) DrawTo (canvas tomo.Canvas) { core.canvas = canvas + if core.drawSizeChange != nil { + core.drawSizeChange() + } } // OnDamage fulfils the tomo.Element interface. This should not need to be @@ -84,28 +87,27 @@ func (core *Core) OnMinimumSizeChange (callback func ()) { // instead kept as a private member. When a Core struct is created, a // corresponding CoreControl struct is linked to it and returned alongside it. type CoreControl struct { - tomo.BasicCanvas core *Core } // HasImage returns true if the core has an allocated image buffer, and false if // it doesn't. func (control CoreControl) HasImage () (has bool) { - return control.core.canvas != nil + return control.core.canvas != nil && !control.core.canvas.Bounds().Empty() } // DamageRegion pushes the selected region of pixels to the parent element. This // does not need to be called when responding to a resize event. func (control CoreControl) DamageRegion (bounds image.Rectangle) { if control.core.onDamage != nil { - control.core.onDamage(tomo.Cut(control, bounds)) + control.core.onDamage(tomo.Cut(control.core, bounds)) } } // DamageAll pushes all pixels to the parent element. This does not need to be // called when redrawing in response to a change in size. func (control CoreControl) DamageAll () { - control.DamageRegion(control.Bounds()) + control.DamageRegion(control.core.Bounds()) } // SetMinimumSize sets the minimum size of this element, notifying the parent diff --git a/elements/fun/clock.go b/elements/fun/clock.go index 1e98029..e41c930 100644 --- a/elements/fun/clock.go +++ b/elements/fun/clock.go @@ -35,7 +35,7 @@ func (element *AnalogClock) SetTime (newTime time.Time) { } func (element *AnalogClock) draw () { - bounds := element.core.Bounds() + bounds := element.Bounds() pattern, inset := theme.SunkenPattern(theme.PatternState { Case: clockCase, @@ -81,7 +81,7 @@ func (element *AnalogClock) radialLine ( outer float64, radian float64, ) { - bounds := element.core.Bounds() + bounds := element.Bounds() width := float64(bounds.Dx()) / 2 height := float64(bounds.Dy()) / 2 min := image.Pt ( @@ -91,5 +91,5 @@ func (element *AnalogClock) radialLine ( int(math.Cos(radian) * outer * width + width), int(math.Sin(radian) * outer * height + height)) // println(min.String(), max.String()) - artist.Line(element.core, source, 1, min, max) + artist.Line(element, source, 1, min, max) } diff --git a/elements/testing/mouse.go b/elements/testing/mouse.go index 138238a..c0cdece 100644 --- a/elements/testing/mouse.go +++ b/elements/testing/mouse.go @@ -29,17 +29,17 @@ func NewMouse () (element *Mouse) { func (element *Mouse) draw () { bounds := element.Bounds() pattern, _ := theme.AccentPattern(theme.PatternState { }) - artist.FillRectangle(element.core, pattern, bounds) + artist.FillRectangle(element, pattern, bounds) artist.StrokeRectangle ( - element.core, + element, artist.NewUniform(color.Black), 1, bounds) artist.Line ( - element.core, artist.NewUniform(color.White), 1, + element, artist.NewUniform(color.White), 1, image.Pt(1, 1), image.Pt(bounds.Dx() - 2, bounds.Dy() - 2)) artist.Line ( - element.core, artist.NewUniform(color.White), 1, + element, artist.NewUniform(color.White), 1, image.Pt(1, bounds.Dy() - 2), image.Pt(bounds.Dx() - 2, 1)) } @@ -53,7 +53,7 @@ func (element *Mouse) HandleMouseUp (x, y int, button tomo.Button) { element.drawing = false mousePos := image.Pt(x, y) element.core.DamageRegion (artist.Line ( - element.core, element.color, 1, + element, element.color, 1, element.lastMousePos, mousePos)) element.lastMousePos = mousePos } @@ -62,7 +62,7 @@ func (element *Mouse) HandleMouseMove (x, y int) { if !element.drawing { return } mousePos := image.Pt(x, y) element.core.DamageRegion (artist.Line ( - element.core, element.color, 1, + element, element.color, 1, element.lastMousePos, mousePos)) element.lastMousePos = mousePos } -- 2.30.2 From b0ff1ca0af43cdb374d7d2e5dfa46d3a39cfc204 Mon Sep 17 00:00:00 2001 From: Sasha Koshka Date: Tue, 31 Jan 2023 18:04:12 -0500 Subject: [PATCH 07/10] Vertical stack example works --- canvas.go | 1 + elements/basic/button.go | 2 +- elements/basic/container.go | 17 ++++++----------- elements/basic/label.go | 6 +----- layout.go | 2 +- layouts/dialog.go | 2 +- layouts/horizontal.go | 2 +- layouts/vertical.go | 29 +++++++++++++---------------- 8 files changed, 25 insertions(+), 36 deletions(-) diff --git a/canvas.go b/canvas.go index 51cd06b..bdfc244 100644 --- a/canvas.go +++ b/canvas.go @@ -63,6 +63,7 @@ func (canvas BasicCanvas) Buffer () (data []color.RGBA, stride int) { // Cut returns a sub-canvas of a given canvas. func Cut (canvas Canvas, bounds image.Rectangle) (reduced BasicCanvas) { + // println(canvas.Bounds().String(), bounds.String()) bounds = bounds.Intersect(canvas.Bounds()) if bounds.Empty() { return } reduced.rect = bounds diff --git a/elements/basic/button.go b/elements/basic/button.go index f9a011f..b9055b5 100644 --- a/elements/basic/button.go +++ b/elements/basic/button.go @@ -137,7 +137,7 @@ func (element *Button) draw () { textBounds := element.drawer.LayoutBounds() offset := image.Point { X: innerBounds.Min.X + (innerBounds.Dx() - textBounds.Dx()) / 2, - Y: innerBounds.Min.X + (innerBounds.Dy() - textBounds.Dy()) / 2, + Y: innerBounds.Min.Y + (innerBounds.Dy() - textBounds.Dy()) / 2, } // account for the fact that the bounding rectangle will be shifted over diff --git a/elements/basic/container.go b/elements/basic/container.go index 2d30952..a23cdc4 100644 --- a/elements/basic/container.go +++ b/elements/basic/container.go @@ -207,7 +207,7 @@ func (element *Container) redoAll () { }) artist.FillRectangle(element, pattern, bounds) - // resize all elements, having them draw onto us + // cut our canvas up and give peices to child elements for _, entry := range element.children { entry.DrawTo(tomo.Cut(element, entry.Bounds)) } @@ -217,31 +217,27 @@ func (element *Container) HandleMouseDown (x, y int, button tomo.Button) { child, handlesMouse := element.ChildAt(image.Pt(x, y)).(tomo.MouseTarget) if !handlesMouse { return } element.drags[button] = child - childPosition := element.childPosition(child) - child.HandleMouseDown(x - childPosition.X, y - childPosition.Y, button) + child.HandleMouseDown(x, y, button) } func (element *Container) HandleMouseUp (x, y int, button tomo.Button) { child := element.drags[button] if child == nil { return } element.drags[button] = nil - childPosition := element.childPosition(child) - child.HandleMouseUp(x - childPosition.X, y - childPosition.Y, button) + child.HandleMouseUp(x, y, button) } func (element *Container) HandleMouseMove (x, y int) { for _, child := range element.drags { if child == nil { continue } - childPosition := element.childPosition(child) - child.HandleMouseMove(x - childPosition.X, y - childPosition.Y) + child.HandleMouseMove(x, y) } } func (element *Container) HandleMouseScroll (x, y int, deltaX, deltaY float64) { child, handlesMouse := element.ChildAt(image.Pt(x, y)).(tomo.MouseTarget) if !handlesMouse { return } - childPosition := element.childPosition(child) - child.HandleMouseScroll(x - childPosition.X, y - childPosition.Y, deltaX, deltaY) + child.HandleMouseScroll(x, y, deltaX, deltaY) } func (element *Container) HandleKeyDown (key tomo.Key, modifiers tomo.Modifiers) { @@ -475,6 +471,5 @@ func (element *Container) updateMinimumSize () { } func (element *Container) recalculate () { - bounds := element.Bounds() - element.layout.Arrange(element.children, bounds.Dx(), bounds.Dy()) + element.layout.Arrange(element.children, element.Bounds()) } diff --git a/elements/basic/label.go b/elements/basic/label.go index 70a8581..5189422 100644 --- a/elements/basic/label.go +++ b/elements/basic/label.go @@ -1,6 +1,5 @@ package basic -import "image" import "git.tebibyte.media/sashakoshka/tomo/theme" import "git.tebibyte.media/sashakoshka/tomo/artist" import "git.tebibyte.media/sashakoshka/tomo/elements/core" @@ -119,8 +118,5 @@ func (element *Label) draw () { foreground, _ := theme.ForegroundPattern (theme.PatternState { Case: labelCase, }) - element.drawer.Draw (element, foreground, image.Point { - X: 0 - textBounds.Min.X, - Y: 0 - textBounds.Min.Y, - }) + element.drawer.Draw (element, foreground, bounds.Min.Sub(textBounds.Min)) } diff --git a/layout.go b/layout.go index e107cba..b6dc034 100644 --- a/layout.go +++ b/layout.go @@ -17,7 +17,7 @@ type Layout interface { // and changes the position of the entiries in the slice so that they // are properly laid out. The given width and height should not be less // than what is returned by MinimumSize. - Arrange (entries []LayoutEntry, width, height int) + Arrange (entries []LayoutEntry, bounds image.Rectangle) // MinimumSize returns the minimum width and height that the layout // needs to properly arrange the given slice of layout entries. diff --git a/layouts/dialog.go b/layouts/dialog.go index 224dad4..fa82d20 100644 --- a/layouts/dialog.go +++ b/layouts/dialog.go @@ -96,7 +96,7 @@ func (layout Dialog) Arrange (entries []tomo.LayoutEntry, width, height int) { entryBounds := entry.Bounds if entryBounds.Dy() != controlRowHeight || entryBounds.Dx() != entryWidth { - entry.Bounds.Max = entryBounds.Min.Add ( + entries[index].Bounds.Max = entryBounds.Min.Add ( image.Pt(entryWidth, controlRowHeight)) } } diff --git a/layouts/horizontal.go b/layouts/horizontal.go index 6fd0743..f891348 100644 --- a/layouts/horizontal.go +++ b/layouts/horizontal.go @@ -45,7 +45,7 @@ func (layout Horizontal) Arrange (entries []tomo.LayoutEntry, width, height int) x += entryWidth entryBounds := entry.Bounds if entryBounds.Dy() != height || entryBounds.Dx() != entryWidth { - entry.Bounds.Max = entryBounds.Min.Add ( + entries[index].Bounds.Max = entryBounds.Min.Add ( image.Pt(entryWidth, height)) } } diff --git a/layouts/vertical.go b/layouts/vertical.go index 365e4b3..56fc23a 100644 --- a/layouts/vertical.go +++ b/layouts/vertical.go @@ -17,22 +17,21 @@ type Vertical struct { } // Arrange arranges a list of entries vertically. -func (layout Vertical) Arrange (entries []tomo.LayoutEntry, width, height int) { +func (layout Vertical) Arrange (entries []tomo.LayoutEntry, bounds image.Rectangle) { if layout.Pad { - width -= theme.Margin() * 2 - height -= theme.Margin() * 2 + bounds = bounds.Inset(theme.Margin()) } - freeSpace := height - expandingElements := 0 // count the number of expanding elements and the amount of free space // for them to collectively occupy, while gathering minimum heights. + freeSpace := bounds.Dy() minimumHeights := make([]int, len(entries)) + expandingElements := 0 for index, entry := range entries { var entryMinHeight int if child, flexible := entry.Element.(tomo.Flexible); flexible { - entryMinHeight = child.FlexibleHeightFor(width) + entryMinHeight = child.FlexibleHeightFor(bounds.Dx()) } else { _, entryMinHeight = entry.MinimumSize() } @@ -47,34 +46,32 @@ func (layout Vertical) Arrange (entries []tomo.LayoutEntry, width, height int) { freeSpace -= theme.Margin() } } + expandingElementHeight := 0 if expandingElements > 0 { expandingElementHeight = freeSpace / expandingElements } - x, y := 0, 0 - if layout.Pad { - x += theme.Margin() - y += theme.Margin() - } + dot := bounds.Min // set the size and position of each element for index, entry := range entries { - if index > 0 && layout.Gap { y += theme.Margin() } + if index > 0 && layout.Gap { dot.Y += theme.Margin() } - entries[index].Bounds.Min = image.Pt(x, y) + entry.Bounds.Min = dot entryHeight := 0 if entry.Expand { entryHeight = expandingElementHeight } else { entryHeight = minimumHeights[index] } - y += entryHeight + dot.Y += entryHeight entryBounds := entry.Bounds - if entryBounds.Dx() != width || entryBounds.Dy() != entryHeight { + if entryBounds.Dx() != bounds.Dx() || entryBounds.Dy() != entryHeight { entry.Bounds.Max = entryBounds.Min.Add ( - image.Pt(width, entryHeight)) + image.Pt(bounds.Dx(), entryHeight)) } + entries[index] = entry } } -- 2.30.2 From 541d0f42042b6fe31361386ac65a28e6ecee3a68 Mon Sep 17 00:00:00 2001 From: Sasha Koshka Date: Tue, 31 Jan 2023 18:06:55 -0500 Subject: [PATCH 08/10] Horizontal layouts now work --- layouts/horizontal.go | 32 ++++++++++++++------------------ layouts/vertical.go | 4 +--- 2 files changed, 15 insertions(+), 21 deletions(-) diff --git a/layouts/horizontal.go b/layouts/horizontal.go index f891348..93339fc 100644 --- a/layouts/horizontal.go +++ b/layouts/horizontal.go @@ -17,37 +17,33 @@ type Horizontal struct { } // Arrange arranges a list of entries horizontally. -func (layout Horizontal) Arrange (entries []tomo.LayoutEntry, width, height int) { - if layout.Pad { - width -= theme.Margin() * 2 - height -= theme.Margin() * 2 - } - // get width of expanding elements - expandingElementWidth := layout.expandingElementWidth(entries, width) +func (layout Horizontal) Arrange (entries []tomo.LayoutEntry, bounds image.Rectangle) { + if layout.Pad { bounds = bounds.Inset(theme.Margin()) } - x, y := 0, 0 - if layout.Pad { - x += theme.Margin() - y += theme.Margin() - } + // get width of expanding elements + expandingElementWidth := layout.expandingElementWidth(entries, bounds.Dx()) + + dot := bounds.Min // set the size and position of each element for index, entry := range entries { - if index > 0 && layout.Gap { x += theme.Margin() } + if index > 0 && layout.Gap { dot.X += theme.Margin() } - entries[index].Bounds.Min = image.Pt(x, y) + entry.Bounds.Min = dot entryWidth := 0 if entry.Expand { entryWidth = expandingElementWidth } else { entryWidth, _ = entry.MinimumSize() } - x += entryWidth + dot.X += entryWidth entryBounds := entry.Bounds - if entryBounds.Dy() != height || entryBounds.Dx() != entryWidth { - entries[index].Bounds.Max = entryBounds.Min.Add ( - image.Pt(entryWidth, height)) + if entryBounds.Dy() != bounds.Dy() || entryBounds.Dx() != entryWidth { + entry.Bounds.Max = entryBounds.Min.Add ( + image.Pt(entryWidth, bounds.Dy())) } + + entries[index] = entry } } diff --git a/layouts/vertical.go b/layouts/vertical.go index 56fc23a..a400e46 100644 --- a/layouts/vertical.go +++ b/layouts/vertical.go @@ -18,9 +18,7 @@ type Vertical struct { // Arrange arranges a list of entries vertically. func (layout Vertical) Arrange (entries []tomo.LayoutEntry, bounds image.Rectangle) { - if layout.Pad { - bounds = bounds.Inset(theme.Margin()) - } + if layout.Pad { bounds = bounds.Inset(theme.Margin()) } // count the number of expanding elements and the amount of free space // for them to collectively occupy, while gathering minimum heights. -- 2.30.2 From 9b22e80f054e0f6f62a510724d24d3d9418ebf20 Mon Sep 17 00:00:00 2001 From: Sasha Koshka Date: Tue, 31 Jan 2023 18:39:17 -0500 Subject: [PATCH 09/10] Got scroll container working --- elements/basic/checkbox.go | 6 +++--- elements/basic/scrollcontainer.go | 20 ++++++++++++++++---- elements/basic/switch.go | 8 ++++---- elements/basic/textbox.go | 9 +++++---- elements/fun/clock.go | 8 ++++---- elements/testing/artist.go | 4 ++-- elements/testing/mouse.go | 8 ++++---- layouts/dialog.go | 25 ++++++++++++------------- layouts/horizontal.go | 3 +-- layouts/vertical.go | 3 +-- 10 files changed, 52 insertions(+), 42 deletions(-) diff --git a/elements/basic/checkbox.go b/elements/basic/checkbox.go index 839bf21..c4a0f78 100644 --- a/elements/basic/checkbox.go +++ b/elements/basic/checkbox.go @@ -134,7 +134,7 @@ func (element *Checkbox) SetText (text string) { func (element *Checkbox) draw () { bounds := element.Bounds() - boxBounds := image.Rect(0, 0, bounds.Dy(), bounds.Dy()) + boxBounds := image.Rect(0, 0, bounds.Dy(), bounds.Dy()).Add(bounds.Min) backgroundPattern, _ := theme.BackgroundPattern(theme.PatternState { Case: checkboxCase, @@ -150,9 +150,9 @@ func (element *Checkbox) draw () { artist.FillRectangle(element, pattern, boxBounds) textBounds := element.drawer.LayoutBounds() - offset := image.Point { + offset := bounds.Min.Add(image.Point { X: bounds.Dy() + theme.Padding(), - } + }) offset.Y -= textBounds.Min.Y offset.X -= textBounds.Min.X diff --git a/elements/basic/scrollcontainer.go b/elements/basic/scrollcontainer.go index f5cb8c3..c9491e1 100644 --- a/elements/basic/scrollcontainer.go +++ b/elements/basic/scrollcontainer.go @@ -57,7 +57,7 @@ func NewScrollContainer (horizontal, vertical bool) (element *ScrollContainer) { func (element *ScrollContainer) handleResize () { element.recalculate() - element.child.DrawTo(tomo.Cut(element, element.child.Bounds())) + element.resizeChildToFit() element.draw() } @@ -91,7 +91,7 @@ func (element *ScrollContainer) Adopt (child tomo.Scrollable) { element.vertical.enabled = element.child.ScrollAxes() if element.core.HasImage() { - element.child.DrawTo(tomo.Cut(element, element.child.Bounds())) + element.resizeChildToFit() } } } @@ -113,7 +113,8 @@ func (element *ScrollContainer) HandleMouseDown (x, y int, button tomo.Button) { if point.In(element.horizontal.bar) { element.horizontal.dragging = true element.horizontal.dragOffset = - point.Sub(element.horizontal.bar.Min).X + x - element.horizontal.bar.Min.X + + element.Bounds().Min.X element.dragHorizontalBar(point) } else if point.In(element.horizontal.gutter) { @@ -128,7 +129,8 @@ func (element *ScrollContainer) HandleMouseDown (x, y int, button tomo.Button) { } else if point.In(element.vertical.bar) { element.vertical.dragging = true element.vertical.dragOffset = - point.Sub(element.vertical.bar.Min).Y + y - element.vertical.bar.Min.Y + + element.Bounds().Min.Y element.dragVerticalBar(point) } else if point.In(element.vertical.gutter) { @@ -268,6 +270,14 @@ func (element *ScrollContainer) clearChildEventHandlers (child tomo.Scrollable) } } +func (element *ScrollContainer) resizeChildToFit () { + childBounds := image.Rect ( + 0, 0, + element.childWidth, + element.childHeight).Add(element.Bounds().Min) + element.child.DrawTo(tomo.Cut(element, childBounds)) +} + func (element *ScrollContainer) recalculate () { _, gutterInsetHorizontal := theme.GutterPattern(theme.PatternState { Case: scrollBarHorizontalCase, @@ -300,6 +310,7 @@ func (element *ScrollContainer) recalculate () { // if enabled, give substance to the gutters if horizontal.exists { + horizontal.gutter.Min.X = bounds.Min.X horizontal.gutter.Min.Y = bounds.Max.Y - thicknessHorizontal horizontal.gutter.Max.X = bounds.Max.X horizontal.gutter.Max.Y = bounds.Max.Y @@ -312,6 +323,7 @@ func (element *ScrollContainer) recalculate () { if vertical.exists { vertical.gutter.Min.X = bounds.Max.X - thicknessVertical vertical.gutter.Max.X = bounds.Max.X + vertical.gutter.Min.Y = bounds.Min.Y vertical.gutter.Max.Y = bounds.Max.Y if horizontal.exists { vertical.gutter.Max.Y -= thicknessHorizontal diff --git a/elements/basic/switch.go b/elements/basic/switch.go index d3ac782..a30523a 100644 --- a/elements/basic/switch.go +++ b/elements/basic/switch.go @@ -141,8 +141,8 @@ func (element *Switch) calculateMinimumSize () { func (element *Switch) draw () { bounds := element.Bounds() - handleBounds := image.Rect(0, 0, bounds.Dy(), bounds.Dy()) - gutterBounds := image.Rect(0, 0, bounds.Dy() * 2, bounds.Dy()) + handleBounds := image.Rect(0, 0, bounds.Dy(), bounds.Dy()).Add(bounds.Min) + gutterBounds := image.Rect(0, 0, bounds.Dy() * 2, bounds.Dy()).Add(bounds.Min) backgroundPattern, _ := theme.BackgroundPattern(theme.PatternState { Case: switchCase, }) @@ -179,9 +179,9 @@ func (element *Switch) draw () { artist.FillRectangle(element, handlePattern, handleBounds) textBounds := element.drawer.LayoutBounds() - offset := image.Point { + offset := bounds.Min.Add(image.Point { X: bounds.Dy() * 2 + theme.Padding(), - } + }) offset.Y -= textBounds.Min.Y offset.X -= textBounds.Min.X diff --git a/elements/basic/textbox.go b/elements/basic/textbox.go index 67cccef..b1028b1 100644 --- a/elements/basic/textbox.go +++ b/elements/basic/textbox.go @@ -258,6 +258,7 @@ func (element *TextBox) scrollToCursor () { if !element.core.HasImage() { return } bounds := element.Bounds().Inset(theme.Padding()) + bounds = bounds.Sub(bounds.Min) bounds.Max.X -= element.valueDrawer.Em().Round() cursorPosition := element.valueDrawer.PositionOf(element.cursor) cursorPosition.X -= element.scroll @@ -285,10 +286,10 @@ func (element *TextBox) draw () { if len(element.text) == 0 && !element.Focused() { // draw placeholder textBounds := element.placeholderDrawer.LayoutBounds() - offset := image.Point { + offset := bounds.Min.Add (image.Point { X: theme.Padding() + inset[3], Y: theme.Padding() + inset[0], - } + }) foreground, _ := theme.ForegroundPattern(theme.PatternState { Case: textBoxCase, Disabled: true, @@ -300,10 +301,10 @@ func (element *TextBox) draw () { } else { // draw input value textBounds := element.valueDrawer.LayoutBounds() - offset := image.Point { + offset := bounds.Min.Add (image.Point { X: theme.Padding() + inset[3] - element.scroll, Y: theme.Padding() + inset[0], - } + }) foreground, _ := theme.ForegroundPattern(theme.PatternState { Case: textBoxCase, Disabled: !element.Enabled(), diff --git a/elements/fun/clock.go b/elements/fun/clock.go index e41c930..facbc8f 100644 --- a/elements/fun/clock.go +++ b/elements/fun/clock.go @@ -84,12 +84,12 @@ func (element *AnalogClock) radialLine ( bounds := element.Bounds() width := float64(bounds.Dx()) / 2 height := float64(bounds.Dy()) / 2 - min := image.Pt ( + min := element.Bounds().Min.Add(image.Pt ( int(math.Cos(radian) * inner * width + width), - int(math.Sin(radian) * inner * height + height)) - max := image.Pt ( + int(math.Sin(radian) * inner * height + height))) + max := element.Bounds().Min.Add(image.Pt ( int(math.Cos(radian) * outer * width + width), - int(math.Sin(radian) * outer * height + height)) + int(math.Sin(radian) * outer * height + height))) // println(min.String(), max.String()) artist.Line(element, source, 1, min, max) } diff --git a/elements/testing/artist.go b/elements/testing/artist.go index 145c629..f9a2290 100644 --- a/elements/testing/artist.go +++ b/elements/testing/artist.go @@ -26,8 +26,8 @@ func NewArtist () (element *Artist) { func (element *Artist) draw () { bounds := element.Bounds() - element.cellBounds.Max.X = bounds.Dx() / 5 - element.cellBounds.Max.Y = (bounds.Dy() - 48) / 8 + element.cellBounds.Max.X = bounds.Min.X + bounds.Dx() / 5 + element.cellBounds.Max.Y = bounds.Min.Y + (bounds.Dy() - 48) / 8 drawStart := time.Now() diff --git a/elements/testing/mouse.go b/elements/testing/mouse.go index c0cdece..421029c 100644 --- a/elements/testing/mouse.go +++ b/elements/testing/mouse.go @@ -36,12 +36,12 @@ func (element *Mouse) draw () { bounds) artist.Line ( element, artist.NewUniform(color.White), 1, - image.Pt(1, 1), - image.Pt(bounds.Dx() - 2, bounds.Dy() - 2)) + bounds.Min.Add(image.Pt(1, 1)), + bounds.Min.Add(image.Pt(bounds.Dx() - 2, bounds.Dy() - 2))) artist.Line ( element, artist.NewUniform(color.White), 1, - image.Pt(1, bounds.Dy() - 2), - image.Pt(bounds.Dx() - 2, 1)) + bounds.Min.Add(image.Pt(1, bounds.Dy() - 2)), + bounds.Min.Add(image.Pt(bounds.Dx() - 2, 1))) } func (element *Mouse) HandleMouseDown (x, y int, button tomo.Button) { diff --git a/layouts/dialog.go b/layouts/dialog.go index fa82d20..f782bb1 100644 --- a/layouts/dialog.go +++ b/layouts/dialog.go @@ -18,13 +18,12 @@ type Dialog struct { Pad bool } -// Arrange arranges a list of entries into a dialog. -func (layout Dialog) Arrange (entries []tomo.LayoutEntry, width, height int) { - if layout.Pad { - width -= theme.Margin() * 2 - height -= theme.Margin() * 2 - } +// FIXME +// Arrange arranges a list of entries into a dialog. +func (layout Dialog) Arrange (entries []tomo.LayoutEntry, bounds image.Rectangle) { + if layout.Pad { bounds = bounds.Inset(theme.Margin()) } + controlRowWidth, controlRowHeight := 0, 0 if len(entries) > 1 { controlRowWidth, @@ -37,19 +36,19 @@ func (layout Dialog) Arrange (entries []tomo.LayoutEntry, width, height int) { entries[0].Bounds.Min.X += theme.Margin() entries[0].Bounds.Min.Y += theme.Margin() } - mainHeight := height - controlRowHeight + mainHeight := bounds.Dy() - controlRowHeight if layout.Gap { mainHeight -= theme.Margin() } mainBounds := entries[0].Bounds - if mainBounds.Dy() != mainHeight || mainBounds.Dx() != width { + if mainBounds.Dy() != mainHeight || mainBounds.Dx() != bounds.Dx() { entries[0].Bounds.Max = - mainBounds.Min.Add(image.Pt(width, mainHeight)) + mainBounds.Min.Add(image.Pt(bounds.Dx(), mainHeight)) } } if len(entries) > 1 { - freeSpace := width + freeSpace := bounds.Dx() expandingElements := 0 // count the number of expanding elements and the amount of free @@ -71,15 +70,15 @@ func (layout Dialog) Arrange (entries []tomo.LayoutEntry, width, height int) { } // determine starting position and dimensions for control row - x, y := 0, height - controlRowHeight + x, y := 0, bounds.Dy() - controlRowHeight if expandingElements == 0 { - x = width - controlRowWidth + x = bounds.Dx() - controlRowWidth } if layout.Pad { x += theme.Margin() y += theme.Margin() } - height -= controlRowHeight + bounds.Max.Y -= controlRowHeight // set the size and position of each element in the control row for index, entry := range entries[1:] { diff --git a/layouts/horizontal.go b/layouts/horizontal.go index 93339fc..3d29518 100644 --- a/layouts/horizontal.go +++ b/layouts/horizontal.go @@ -22,10 +22,9 @@ func (layout Horizontal) Arrange (entries []tomo.LayoutEntry, bounds image.Recta // get width of expanding elements expandingElementWidth := layout.expandingElementWidth(entries, bounds.Dx()) - - dot := bounds.Min // set the size and position of each element + dot := bounds.Min for index, entry := range entries { if index > 0 && layout.Gap { dot.X += theme.Margin() } diff --git a/layouts/vertical.go b/layouts/vertical.go index a400e46..f76b96d 100644 --- a/layouts/vertical.go +++ b/layouts/vertical.go @@ -50,9 +50,8 @@ func (layout Vertical) Arrange (entries []tomo.LayoutEntry, bounds image.Rectang expandingElementHeight = freeSpace / expandingElements } - dot := bounds.Min - // set the size and position of each element + dot := bounds.Min for index, entry := range entries { if index > 0 && layout.Gap { dot.Y += theme.Margin() } -- 2.30.2 From 9cb0d064ffffeb9b0bee62091fb87350d78abacf Mon Sep 17 00:00:00 2001 From: Sasha Koshka Date: Tue, 31 Jan 2023 18:57:29 -0500 Subject: [PATCH 10/10] Dialog layout is all good --- layouts/dialog.go | 34 +++++++++++----------------------- layouts/horizontal.go | 6 +----- layouts/vertical.go | 5 +---- 3 files changed, 13 insertions(+), 32 deletions(-) diff --git a/layouts/dialog.go b/layouts/dialog.go index f782bb1..836d4b3 100644 --- a/layouts/dialog.go +++ b/layouts/dialog.go @@ -18,8 +18,6 @@ type Dialog struct { Pad bool } -// FIXME - // Arrange arranges a list of entries into a dialog. func (layout Dialog) Arrange (entries []tomo.LayoutEntry, bounds image.Rectangle) { if layout.Pad { bounds = bounds.Inset(theme.Margin()) } @@ -31,20 +29,14 @@ func (layout Dialog) Arrange (entries []tomo.LayoutEntry, bounds image.Rectangle } if len(entries) > 0 { - entries[0].Bounds.Min = image.Point { } - if layout.Pad { - entries[0].Bounds.Min.X += theme.Margin() - entries[0].Bounds.Min.Y += theme.Margin() - } + main := entries[0] + main.Bounds.Min = bounds.Min mainHeight := bounds.Dy() - controlRowHeight if layout.Gap { mainHeight -= theme.Margin() } - mainBounds := entries[0].Bounds - if mainBounds.Dy() != mainHeight || mainBounds.Dx() != bounds.Dx() { - entries[0].Bounds.Max = - mainBounds.Min.Add(image.Pt(bounds.Dx(), mainHeight)) - } + main.Bounds.Max = main.Bounds.Min.Add(image.Pt(bounds.Dx(), mainHeight)) + entries[0] = main } if len(entries) > 1 { @@ -70,34 +62,30 @@ func (layout Dialog) Arrange (entries []tomo.LayoutEntry, bounds image.Rectangle } // determine starting position and dimensions for control row - x, y := 0, bounds.Dy() - controlRowHeight + dot := image.Pt(bounds.Min.X, bounds.Max.Y - controlRowHeight) if expandingElements == 0 { - x = bounds.Dx() - controlRowWidth + dot.X = bounds.Max.X - controlRowWidth } - if layout.Pad { - x += theme.Margin() - y += theme.Margin() - } - bounds.Max.Y -= controlRowHeight // set the size and position of each element in the control row for index, entry := range entries[1:] { - if index > 0 && layout.Gap { x += theme.Margin() } + if index > 0 && layout.Gap { dot.X += theme.Margin() } - entries[index + 1].Bounds.Min = image.Pt(x, y) + entry.Bounds.Min = dot entryWidth := 0 if entry.Expand { entryWidth = expandingElementWidth } else { entryWidth, _ = entry.MinimumSize() } - x += entryWidth + dot.X += entryWidth entryBounds := entry.Bounds if entryBounds.Dy() != controlRowHeight || entryBounds.Dx() != entryWidth { - entries[index].Bounds.Max = entryBounds.Min.Add ( + entry.Bounds.Max = entryBounds.Min.Add ( image.Pt(entryWidth, controlRowHeight)) } + entries[index + 1] = entry } } diff --git a/layouts/horizontal.go b/layouts/horizontal.go index 3d29518..95c414b 100644 --- a/layouts/horizontal.go +++ b/layouts/horizontal.go @@ -36,11 +36,7 @@ func (layout Horizontal) Arrange (entries []tomo.LayoutEntry, bounds image.Recta entryWidth, _ = entry.MinimumSize() } dot.X += entryWidth - entryBounds := entry.Bounds - if entryBounds.Dy() != bounds.Dy() || entryBounds.Dx() != entryWidth { - entry.Bounds.Max = entryBounds.Min.Add ( - image.Pt(entryWidth, bounds.Dy())) - } + entry.Bounds.Max = entry.Bounds.Min.Add(image.Pt(entryWidth, bounds.Dy())) entries[index] = entry } diff --git a/layouts/vertical.go b/layouts/vertical.go index f76b96d..9455bc0 100644 --- a/layouts/vertical.go +++ b/layouts/vertical.go @@ -64,10 +64,7 @@ func (layout Vertical) Arrange (entries []tomo.LayoutEntry, bounds image.Rectang } dot.Y += entryHeight entryBounds := entry.Bounds - if entryBounds.Dx() != bounds.Dx() || entryBounds.Dy() != entryHeight { - entry.Bounds.Max = entryBounds.Min.Add ( - image.Pt(bounds.Dx(), entryHeight)) - } + entry.Bounds.Max = entryBounds.Min.Add(image.Pt(bounds.Dx(), entryHeight)) entries[index] = entry } } -- 2.30.2