From 51084a6cfe016d25463f57195a91a848cebfa384 Mon Sep 17 00:00:00 2001 From: Sasha Koshka Date: Sat, 11 Mar 2023 00:43:26 -0500 Subject: [PATCH 01/17] Removed references to flexible from layouts, x backend, core --- backends/x/window.go | 27 +-------------------- elements/core/propagator.go | 10 -------- layouts/basic/dialog.go | 40 ------------------------------- layouts/basic/horizontal.go | 44 ----------------------------------- layouts/basic/vertical.go | 40 +------------------------------ layouts/layout.go | 16 ------------- theme/assets/wintergreen.png | Bin 1948 -> 1929 bytes 7 files changed, 2 insertions(+), 175 deletions(-) diff --git a/backends/x/window.go b/backends/x/window.go index 8150076..f3981cb 100644 --- a/backends/x/window.go +++ b/backends/x/window.go @@ -96,9 +96,6 @@ func (window *Window) Adopt (child elements.Element) { window.child.OnDamage(nil) window.child.OnMinimumSizeChange(nil) } - if previousChild, ok := window.child.(elements.Flexible); ok { - previousChild.OnFlexibleHeightChange(nil) - } if previousChild, ok := window.child.(elements.Focusable); ok { previousChild.OnFocusRequest(nil) previousChild.OnFocusMotionRequest(nil) @@ -115,9 +112,6 @@ func (window *Window) Adopt (child elements.Element) { if newChild, ok := child.(elements.Configurable); ok { newChild.SetConfig(window.config) } - if newChild, ok := child.(elements.Flexible); ok { - newChild.OnFlexibleHeightChange(window.resizeChildToFit) - } if newChild, ok := child.(elements.Focusable); ok { newChild.OnFocusRequest(window.childSelectionRequestCallback) } @@ -263,26 +257,7 @@ func (window *Window) redrawChildEntirely () { func (window *Window) resizeChildToFit () { window.skipChildDrawCallback = true - if child, ok := window.child.(elements.Flexible); ok { - minimumHeight := child.FlexibleHeightFor(window.metrics.width) - minimumWidth, _ := child.MinimumSize() - - icccm.WmNormalHintsSet ( - window.backend.connection, - window.xWindow.Id, - &icccm.NormalHints { - Flags: icccm.SizeHintPMinSize, - MinWidth: uint(minimumWidth), - MinHeight: uint(minimumHeight), - }) - - if window.metrics.height >= minimumHeight && - window.metrics.width >= minimumWidth { - window.child.DrawTo(window.canvas) - } - } else { - window.child.DrawTo(window.canvas) - } + window.child.DrawTo(window.canvas) window.skipChildDrawCallback = false } diff --git a/elements/core/propagator.go b/elements/core/propagator.go index 291d406..2375f5b 100644 --- a/elements/core/propagator.go +++ b/elements/core/propagator.go @@ -326,16 +326,6 @@ func (propagator *Propagator) forFocusable (callback func (child elements.Focusa }) } -func (propagator *Propagator) forFlexible (callback func (child elements.Flexible) bool) { - propagator.forChildren (func (child elements.Element) bool { - typedChild, flexible := child.(elements.Flexible) - if flexible { - if !callback(typedChild) { return false } - } - return true - }) -} - func (propagator *Propagator) firstFocused () int { for index := 0; index < propagator.parent.CountChildren(); index ++ { child, focusable := propagator.parent.Child(index).(elements.Focusable) diff --git a/layouts/basic/dialog.go b/layouts/basic/dialog.go index ed3b45e..b73c987 100644 --- a/layouts/basic/dialog.go +++ b/layouts/basic/dialog.go @@ -3,7 +3,6 @@ package basicLayouts import "image" import "git.tebibyte.media/sashakoshka/tomo/artist" import "git.tebibyte.media/sashakoshka/tomo/layouts" -import "git.tebibyte.media/sashakoshka/tomo/elements" // Dialog arranges elements in the form of a dialog box. The first element is // positioned above as the main focus of the dialog, and is set to expand @@ -132,45 +131,6 @@ func (layout Dialog) MinimumSize ( return } -// FlexibleHeightFor Returns the minimum height the layout needs to lay out the -// specified elements at the given width, taking into account flexible elements. -func (layout Dialog) FlexibleHeightFor ( - entries []layouts.LayoutEntry, - margin image.Point, - padding artist.Inset, - width int, -) ( - height int, -) { - if layout.Pad { - width -= padding.Horizontal() - } - - if len(entries) > 0 { - mainChildHeight := 0 - if child, flexible := entries[0].Element.(elements.Flexible); flexible { - mainChildHeight = child.FlexibleHeightFor(width) - } else { - _, mainChildHeight = entries[0].MinimumSize() - } - height += mainChildHeight - } - - if len(entries) > 1 { - if layout.Gap { height += margin.Y } - _, additionalHeight := layout.minimumSizeOfControlRow ( - entries[1:], margin, padding) - height += additionalHeight - } - - if layout.Pad { - height += padding.Vertical() - } - return -} - -// TODO: possibly flatten this method to account for flexible elements within -// the control row. func (layout Dialog) minimumSizeOfControlRow ( entries []layouts.LayoutEntry, margin image.Point, diff --git a/layouts/basic/horizontal.go b/layouts/basic/horizontal.go index e27c3fe..0819241 100644 --- a/layouts/basic/horizontal.go +++ b/layouts/basic/horizontal.go @@ -3,7 +3,6 @@ package basicLayouts import "image" import "git.tebibyte.media/sashakoshka/tomo/artist" import "git.tebibyte.media/sashakoshka/tomo/layouts" -import "git.tebibyte.media/sashakoshka/tomo/elements" // Horizontal arranges elements horizontally. Elements at the start of the entry // list will be positioned on the left, and elements at the end of the entry @@ -76,49 +75,6 @@ func (layout Horizontal) MinimumSize ( return } -// FlexibleHeightFor Returns the minimum height the layout needs to lay out the -// specified elements at the given width, taking into account flexible elements. -func (layout Horizontal) FlexibleHeightFor ( - entries []layouts.LayoutEntry, - margin image.Point, - padding artist.Inset, - width int, -) ( - height int, -) { - if layout.Pad { width -= padding.Horizontal() } - - // get width of expanding elements - expandingElementWidth := layout.expandingElementWidth ( - entries, margin, padding, width) - - x, y := 0, 0 - if layout.Pad { - x += padding.Horizontal() - y += padding.Vertical() - } - - // set the size and position of each element - for index, entry := range entries { - entryWidth, entryHeight := entry.MinimumSize() - if entry.Expand { - entryWidth = expandingElementWidth - } - if child, flexible := entry.Element.(elements.Flexible); flexible { - entryHeight = child.FlexibleHeightFor(entryWidth) - } - if entryHeight > height { height = entryHeight } - - x += entryWidth - if index > 0 && layout.Gap { x += margin.X } - } - - if layout.Pad { - height += padding.Vertical() - } - return -} - func (layout Horizontal) expandingElementWidth ( entries []layouts.LayoutEntry, margin image.Point, diff --git a/layouts/basic/vertical.go b/layouts/basic/vertical.go index a66c648..bbcfb9a 100644 --- a/layouts/basic/vertical.go +++ b/layouts/basic/vertical.go @@ -3,7 +3,6 @@ package basicLayouts import "image" import "git.tebibyte.media/sashakoshka/tomo/artist" import "git.tebibyte.media/sashakoshka/tomo/layouts" -import "git.tebibyte.media/sashakoshka/tomo/elements" // Vertical arranges elements vertically. Elements at the start of the entry // list will be positioned at the top, and elements at the end of the entry list @@ -32,13 +31,7 @@ func (layout Vertical) Arrange ( minimumHeights := make([]int, len(entries)) expandingElements := 0 for index, entry := range entries { - var entryMinHeight int - - if child, flexible := entry.Element.(elements.Flexible); flexible { - entryMinHeight = child.FlexibleHeightFor(bounds.Dx()) - } else { - _, entryMinHeight = entry.MinimumSize() - } + _, entryMinHeight := entry.MinimumSize() minimumHeights[index] = entryMinHeight if entry.Expand { @@ -101,34 +94,3 @@ func (layout Vertical) MinimumSize ( } return } - -// FlexibleHeightFor Returns the minimum height the layout needs to lay out the -// specified elements at the given width, taking into account flexible elements. -func (layout Vertical) FlexibleHeightFor ( - entries []layouts.LayoutEntry, - margin image.Point, - padding artist.Inset, - width int, -) ( - height int, -) { - if layout.Pad { - width -= padding.Horizontal() - height += padding.Vertical() - } - - for index, entry := range entries { - child, flexible := entry.Element.(elements.Flexible) - if flexible { - height += child.FlexibleHeightFor(width) - } else { - _, entryHeight := entry.MinimumSize() - height += entryHeight - } - - if layout.Gap && index > 0 { - height += margin.Y - } - } - return -} diff --git a/layouts/layout.go b/layouts/layout.go index 786a2b3..d538e5f 100644 --- a/layouts/layout.go +++ b/layouts/layout.go @@ -12,10 +12,6 @@ type LayoutEntry struct { Expand bool } -// TODO: have layouts take in artist.Inset for margin and padding -// TODO: create a layout that only displays the first element and full screen. -// basically a blank layout for containers that only ever have one element. - // Layout is capable of arranging elements within a container. It is also able // to determine the minimum amount of room it needs to do so. type Layout interface { @@ -39,16 +35,4 @@ type Layout interface { ) ( width, height int, ) - - // FlexibleHeightFor Returns the minimum height the layout needs to lay - // out the specified elements at the given width, taking into account - // flexible elements. - FlexibleHeightFor ( - entries []LayoutEntry, - margin image.Point, - padding artist.Inset, - squeeze int, - ) ( - height int, - ) } diff --git a/theme/assets/wintergreen.png b/theme/assets/wintergreen.png index 154a6d8b81561fa518b8abe5ee99d4eafcdc1916..a0cde5ef71fa61a28d0e84ef64147aeb8f89360f 100644 GIT binary patch delta 1825 zcmXX`X;f3!7EXY0C88G}VHCWv6-k*yFw|0#OBj?EpRzy(!z2dMKp;RuFz~{{^JkyE*Ewf>-~P_G3&B6YOgd9@ z?FToKPr`_}Ffz9Sr*lnfiTe7eZo_y$_0|?-ia&z6R*&1wp|~)Iryc5Pmm?=Gn-21# ziV|bL7Kx&VyQRv8(T9%IKuNV9A;OgqiI*A5wu14GV1EcXC8(GdJP$u_dS4pvnzWb8 z`PsS2_os&7|MbRL4YM2&pVy&AW*Jkf;H}`VjDb&xa*?Z&xm#|p<|x2w9oKRAbucaY z9-^OY?*|{l{>*!1qVs!6oIJV;ozD75&7J)-=dM?MlFD(7^82mA_)J`&keimoi$Y_^}ht^CFvLl@mNif%jrx_(DAwCJT^MVlz81-ojKnYx;bY%i4Fs;C)44aL87L}1B*d%MroJy@I{^lwDl1XAYF#25Ek z()3J>M7EsQd-b%hrY?B%UMYWyhfr^QZKrTgxpruKkeGWk2J?co7L!R{sZXnoFT7-pI{!>NIbz$t?1Z^SU= ze`Qo+&E#S7zZZK-H&-4QYi2K`{*M($>;^P8%9VZo_(~V#BR#kE2?z9_6qY51yUdHk zv@W@y)tTi4u&~g|8OeabNm4ebK;~2bI7cZxiv)}s**IuM{WjX7@?v^94$^)YB*oaK zvza*PHRS+T%DXws%VkZ`G7p6OLz9f7Z%9cQo`yR~kBobNuX!kGt- zDCcN8^v!VVs-kHPDXG|BAt(9Vngbd>Um>3!E0@b|4mLCDf)%`%(+88$+&qPxCYPES zyL-+bOzt9cgB7GB&apoI@2U~|na6r_#;UD6I?Q0XChEP44NVNhk|mW;gj2D>cpKZ3 zQMZ&ZLXn|u)=5V?+AN!AfXCjH9&q;P4I4Sn>BJipeCL|#6a|I#Bem-9d59&3s-5{e z^)eD4w*tZPC5|2J@sZQBLD~c45nbV$NvGjULV&K2?wsXVQ9L=+gHYEIycy5=(2RHa@+7mYR3k z+>1#ksN*){SGu0GHAyv6&=@}G#k$l92kgO-pej7}(NXZ6 zPLd~puhYtxWj)@ygcZ)c!->p1_IBop7Xs>5gZiQk#E$}VSL?uIaTYl~UGs8`Jb zd2!zm;DlDgj=mUmIDwmg_0(#vLe&EqN20ji&YPZFj;P+JdcPj~4~#I`QWqb$?;cj> zibO`vOHN@aUbDTJ{71I%4STQBba`~_Ns>@oE<>SgOkUzFhiq_Pm zwv*IUMPHT{PQJoI- zdj2mIlB%b%znxIT1jCz`Mt>hM@SQ~XqqLl}!DS>J(`wyR@y#x&%BE4ogeKh>jN5uC zgf;iKW2i>P&Kw~sgG^9lqG14O8kolI0KbDSr%?*XEBMke@Qg0u3;rk2-JEb7lx;KE wP18}ozQze5daM|H;_R~h9_}{gdJcn|>Wr`4&wH{+4hYry!QIR4flCnk|3WWtFaQ7m delta 1844 zcmY*ZdpwkB8=iyl)`WK|XEM{)mQx5VVP}lkYEz9EqA?8T0z)6`P2SUnqxJ z$93jY-}q2W<-87|$)Sx!Ozbd)9A;m>clN*U`RD%K_jNzdb6wAUKlfcQ2TY5FfSsou z>2A?ulAMK`g*K%4qa}MTM=PrwPVAMnh;h#YhaYI&)iUOPwLS9VFaN61P8|+*5Q)AV zjtRFNsfw_f@qJR)A-g8XS6UydEUcpWh8~O3k!{y$k8clf2hPvskuhPkkZo1>9*dL0 z>aQh}d6X9SfQWrwu0Dx-zbRV9K+slc)spYv>X@p6mzoz>{eECV#lICC=}TUz z5XV{VdC0^ObZ$3rAwPVD+v~7$krMn2$ExJ1@SpV>jd<0^fRykacHF>(3n^7Yc}M#7 zlR_)Of9f9(n;bkua&LtLgZws@hyj-Ly*;~zx4Ui?t*_0Ry@ZnR@#(wulD zI4)fE$!B4JYV)VBUDIY^dfY*>(gkT>=6Cc?;3ej4<13?)p@7>?hkQ_f8tX}@H$LO( z1nnmed(_?{X34XntX0M~CLN|H&)A1iJ2kXy$s7T1ietu4^4j35nz;*S9laJqfEhUL zO5Fx!}33Jr5Lle1XrPB~gaDt{;KB*i4bIlk+ z=Uq~X+}d_2BCISPn5Mib#1Ky78tF960{&@xD=`3b;n4(SQn4L9EThlgeF@mj_zUn` z7PcXjbns>zVFqryL2bB6yLkC`pdHG^Gtdbp(R`_HYtmn4?B7dwgbz{%G1KglNI@rYRIN~cDVHs>H+5ymo$~Jx{tLHWCi`}I ziQNBpiXhoyRQ>t+L)z?XT(d!w>;0f!zoL|LxfRr_nhgy{in%202X2~=@3xK|H!oij zjzKT5-Y=QJ>uw*Ahz-mmAwkmAClbR>MZ2JXH|ZR^hzqe54k9P*Eu7b}y`tA1PN}oE zk72$g=8=7AG1xM)F1A1QW&TS{@4i1h7#ahG6#PH@mG#$G;9uj0!D=7+JOE?wKEC*j zHw^aSE;pfO@d6rL^K`mPc4j!CgmlDZSD&;ODbD8ve8 zfdi63#bsgoZn0Y+%~U%LoRawuFVis@E$0&!m)1FE9b01?6nTB}OmreS)U2U^m*=i~ z|HSce#G~Xq*Cl99>8_SsQDb0ntBtPK9ME*@DJ`x$Rmx?@t~@E=uFWgXxYoh#R=8svx0Pk zQ_Dh1>j+00dU#NO#|BH2<=p81H%ptapPUT%t8q4kaxK5r@IS#ps_`6lw&9}-=v>Jx zE2xoohK?A`BZ~uw-3Y~s33C`W%p-owbcNwBa0s Date: Sat, 11 Mar 2023 00:48:15 -0500 Subject: [PATCH 02/17] Removed references to flexible from containers --- elements/basic/container.go | 41 ------------------------------- elements/basic/scrollcontainer.go | 3 --- 2 files changed, 44 deletions(-) diff --git a/elements/basic/container.go b/elements/basic/container.go index 6728f31..40f215d 100644 --- a/elements/basic/container.go +++ b/elements/basic/container.go @@ -20,12 +20,10 @@ type Container struct { layout layouts.Layout children []layouts.LayoutEntry warping bool - flexible bool config config.Wrapped theme theme.Wrapped - onFlexibleHeightChange func () onFocusRequest func () (granted bool) onFocusMotionRequest func (input.KeynavDirection) (granted bool) } @@ -70,9 +68,6 @@ func (element *Container) Adopt (child elements.Element, expand bool) { element.redoAll() element.core.DamageAll() }) - if child0, ok := child.(elements.Flexible); ok { - child0.OnFlexibleHeightChange(element.notifyFlexibleChange) - } if child0, ok := child.(elements.Focusable); ok { child0.OnFocusRequest (func () (granted bool) { return element.childFocusRequestCallback(child0) @@ -153,9 +148,6 @@ func (element *Container) clearChildEventHandlers (child elements.Element) { child0.HandleUnfocus() } } - if child0, ok := child.(elements.Flexible); ok { - child0.OnFlexibleHeightChange(nil) - } } // DisownAll removes all child elements from the container at once. @@ -251,18 +243,6 @@ func (element *Container) SetConfig (new config.Config) { element.redoAll() } -func (element *Container) FlexibleHeightFor (width int) (height int) { - margin := element.theme.Margin(theme.PatternBackground) - padding := element.theme.Padding(theme.PatternBackground) - return element.layout.FlexibleHeightFor ( - element.children, - margin, padding, width) -} - -func (element *Container) OnFlexibleHeightChange (callback func ()) { - element.onFlexibleHeightChange = callback -} - func (element *Container) OnFocusRequest (callback func () (granted bool)) { element.onFocusRequest = callback element.Propagator.OnFocusRequest(callback) @@ -275,15 +255,6 @@ func (element *Container) OnFocusMotionRequest ( element.Propagator.OnFocusMotionRequest(callback) } -func (element *Container) forFlexible (callback func (child elements.Flexible) bool) { - for _, entry := range element.children { - child, flexible := entry.Element.(elements.Flexible) - if flexible { - if !callback(child) { break } - } - } -} - func (element *Container) reflectChildProperties () { focusable := false for _, entry := range element.children { @@ -296,12 +267,6 @@ func (element *Container) reflectChildProperties () { if !focusable && element.Focused() { element.Propagator.HandleUnfocus() } - - element.flexible = false - element.forFlexible (func (elements.Flexible) bool { - element.flexible = true - return false - }) } func (element *Container) childFocusRequestCallback ( @@ -326,12 +291,6 @@ func (element *Container) updateMinimumSize () { element.core.SetMinimumSize(width, height) } -func (element *Container) notifyFlexibleChange () { - if element.onFlexibleHeightChange != nil { - element.onFlexibleHeightChange() - } -} - func (element *Container) doLayout () { margin := element.theme.Margin(theme.PatternBackground) padding := element.theme.Padding(theme.PatternBackground) diff --git a/elements/basic/scrollcontainer.go b/elements/basic/scrollcontainer.go index cbc53ce..2d5cf20 100644 --- a/elements/basic/scrollcontainer.go +++ b/elements/basic/scrollcontainer.go @@ -131,9 +131,6 @@ func (element *ScrollContainer) clearChildEventHandlers (child elements.Scrollab child0.HandleUnfocus() } } - if child0, ok := child.(elements.Flexible); ok { - child0.OnFlexibleHeightChange(nil) - } } // SetTheme sets the element's theme. From 081b005679ab73e2042bbaedf1b1e93b4b70b275 Mon Sep 17 00:00:00 2001 From: Sasha Koshka Date: Sat, 11 Mar 2023 18:00:29 -0500 Subject: [PATCH 03/17] Added a somewhat buggy DocumentContainer --- elements/basic/documentContainer.go | 342 ++++++++++++++++++++++++++++ elements/basic/scrollcontainer.go | 1 - examples/documentContainer/main.go | 41 ++++ 3 files changed, 383 insertions(+), 1 deletion(-) create mode 100644 elements/basic/documentContainer.go create mode 100644 examples/documentContainer/main.go diff --git a/elements/basic/documentContainer.go b/elements/basic/documentContainer.go new file mode 100644 index 0000000..c525cde --- /dev/null +++ b/elements/basic/documentContainer.go @@ -0,0 +1,342 @@ +package basicElements + +import "image" +import "git.tebibyte.media/sashakoshka/tomo/input" +import "git.tebibyte.media/sashakoshka/tomo/theme" +import "git.tebibyte.media/sashakoshka/tomo/config" +import "git.tebibyte.media/sashakoshka/tomo/canvas" +import "git.tebibyte.media/sashakoshka/tomo/artist" +import "git.tebibyte.media/sashakoshka/tomo/layouts" +import "git.tebibyte.media/sashakoshka/tomo/elements" +import "git.tebibyte.media/sashakoshka/tomo/elements/core" + +type DocumentContainer struct { + *core.Core + *core.Propagator + core core.CoreControl + + children []layouts.LayoutEntry + scroll image.Point + warping bool + contentBounds image.Rectangle + + config config.Wrapped + theme theme.Wrapped + + onFocusRequest func () (granted bool) + onFocusMotionRequest func (input.KeynavDirection) (granted bool) + onScrollBoundsChange func () +} + +// NewDocumentContainer creates a new document container. +func NewDocumentContainer () (element *DocumentContainer) { + element = &DocumentContainer { } + element.theme.Case = theme.C("basic", "documentContainer") + element.Core, element.core = core.NewCore(element.redoAll) + element.Propagator = core.NewPropagator(element) + return +} + +// Adopt adds a new child element to the container. +func (element *DocumentContainer) Adopt (child elements.Element) { + // set event handlers + if child0, ok := child.(elements.Themeable); ok { + child0.SetTheme(element.theme.Theme) + } + if child0, ok := child.(elements.Configurable); ok { + child0.SetConfig(element.config.Config) + } + child.OnDamage (func (region canvas.Canvas) { + element.core.DamageRegion(region.Bounds()) + }) + child.OnMinimumSizeChange (func () { + element.redoAll() + element.core.DamageAll() + }) + if child0, ok := child.(elements.Flexible); ok { + child0.OnFlexibleHeightChange (func () { + element.redoAll() + element.core.DamageAll() + }) + } + if child0, ok := child.(elements.Focusable); ok { + child0.OnFocusRequest (func () (granted bool) { + return element.childFocusRequestCallback(child0) + }) + child0.OnFocusMotionRequest ( + func (direction input.KeynavDirection) (granted bool) { + if element.onFocusMotionRequest == nil { return } + return element.onFocusMotionRequest(direction) + }) + } + + // add child + element.children = append (element.children, layouts.LayoutEntry { + Element: child, + }) + + // refresh stale data + element.reflectChildProperties() + if element.core.HasImage() && !element.warping { + element.redoAll() + element.core.DamageAll() + } +} + +// Warp runs the specified callback, deferring all layout and rendering updates +// until the callback has finished executing. This allows for aplications to +// perform batch gui updates without flickering and stuff. +func (element *DocumentContainer) Warp (callback func ()) { + if element.warping { + callback() + return + } + + element.warping = true + callback() + element.warping = false + + if element.core.HasImage() { + element.redoAll() + element.core.DamageAll() + } +} + +// Disown removes the given child from the container if it is contained within +// it. +func (element *DocumentContainer) Disown (child elements.Element) { + for index, entry := range element.children { + if entry.Element == child { + element.clearChildEventHandlers(entry.Element) + element.children = append ( + element.children[:index], + element.children[index + 1:]...) + break + } + } + + element.reflectChildProperties() + if element.core.HasImage() && !element.warping { + element.redoAll() + element.core.DamageAll() + } +} + +func (element *DocumentContainer) clearChildEventHandlers (child elements.Element) { + child.DrawTo(nil) + child.OnDamage(nil) + child.OnMinimumSizeChange(nil) + if child0, ok := child.(elements.Focusable); ok { + child0.OnFocusRequest(nil) + child0.OnFocusMotionRequest(nil) + if child0.Focused() { + child0.HandleUnfocus() + } + } +} + +// DisownAll removes all child elements from the container at once. +func (element *DocumentContainer) DisownAll () { + for _, entry := range element.children { + element.clearChildEventHandlers(entry.Element) + } + element.children = nil + + element.reflectChildProperties() + if element.core.HasImage() && !element.warping { + element.redoAll() + element.core.DamageAll() + } +} + +// Children returns a slice containing this element's children. +func (element *DocumentContainer) Children () (children []elements.Element) { + children = make([]elements.Element, len(element.children)) + for index, entry := range element.children { + children[index] = entry.Element + } + return +} + +// CountChildren returns the amount of children contained within this element. +func (element *DocumentContainer) CountChildren () (count int) { + return len(element.children) +} + +// Child returns the child at the specified index. If the index is out of +// bounds, this method will return nil. +func (element *DocumentContainer) Child (index int) (child elements.Element) { + if index < 0 || index > len(element.children) { return } + return element.children[index].Element +} + +// ChildAt returns the child that contains the specified x and y coordinates. If +// there are no children at the coordinates, this method will return nil. +func (element *DocumentContainer) ChildAt (point image.Point) (child elements.Element) { + for _, entry := range element.children { + if point.In(entry.Bounds) { + child = entry.Element + } + } + return +} + +func (element *DocumentContainer) redoAll () { + if !element.core.HasImage() { return } + + // do a layout + element.doLayout() + + // draw a background + rocks := make([]image.Rectangle, len(element.children)) + for index, entry := range element.children { + rocks[index] = entry.Bounds + } + pattern := element.theme.Pattern ( + theme.PatternBackground, + theme.State { }) + artist.DrawShatter ( + element.core, pattern, rocks...) + + element.partition() + if element.onScrollBoundsChange != nil { + element.onScrollBoundsChange() + } +} + +func (element *DocumentContainer) partition () { + for _, entry := range element.children { + entry.DrawTo(nil) + } + + // cut our canvas up and give peices to child elements + for _, entry := range element.children { + if entry.Bounds.Overlaps(element.Bounds()) { + entry.DrawTo(canvas.Cut(element.core, entry.Bounds)) + } + } +} + +// SetTheme sets the element's theme. +func (element *DocumentContainer) SetTheme (new theme.Theme) { + if new == element.theme.Theme { return } + element.theme.Theme = new + element.Propagator.SetTheme(new) + element.core.SetMinimumSize ( + element.theme.Padding(theme.PatternBackground).Horizontal(), + element.theme.Padding(theme.PatternBackground).Vertical(),) + element.redoAll() +} + +// SetConfig sets the element's configuration. +func (element *DocumentContainer) SetConfig (new config.Config) { + if new == element.config.Config { return } + element.Propagator.SetConfig(new) + element.redoAll() +} + +func (element *DocumentContainer) OnFocusRequest (callback func () (granted bool)) { + element.onFocusRequest = callback + element.Propagator.OnFocusRequest(callback) +} + +func (element *DocumentContainer) OnFocusMotionRequest ( + callback func (direction input.KeynavDirection) (granted bool), +) { + element.onFocusMotionRequest = callback + element.Propagator.OnFocusMotionRequest(callback) +} + +// ScrollContentBounds returns the full content size of the element. +func (element *DocumentContainer) ScrollContentBounds () image.Rectangle { + return element.contentBounds +} + +// ScrollViewportBounds returns the size and position of the element's +// viewport relative to ScrollBounds. +func (element *DocumentContainer) ScrollViewportBounds () image.Rectangle { + padding := element.theme.Padding(theme.PatternBackground) + bounds := padding.Apply(element.Bounds()) + bounds = bounds.Sub(bounds.Min).Add(element.scroll) + return bounds +} + +// ScrollTo scrolls the viewport to the specified point relative to +// ScrollBounds. +func (element *DocumentContainer) ScrollTo (position image.Point) { + if position.Y < 0 { + position.Y = 0 + } + element.scroll = position + if element.core.HasImage() && !element.warping { + element.redoAll() + element.core.DamageAll() + } +} + +// ScrollAxes returns the supported axes for scrolling. +func (element *DocumentContainer) ScrollAxes () (horizontal, vertical bool) { + return false, true +} + +// OnScrollBoundsChange sets a function to be called when the element's +// ScrollContentBounds, ScrollViewportBounds, or ScrollAxes are changed. +func (element *DocumentContainer) OnScrollBoundsChange(callback func()) { + element.onScrollBoundsChange = callback +} + +func (element *DocumentContainer) reflectChildProperties () { + focusable := false + for _, entry := range element.children { + _, focusable := entry.Element.(elements.Focusable) + if focusable { + focusable = true + break + } + } + if !focusable && element.Focused() { + element.Propagator.HandleUnfocus() + } +} + +func (element *DocumentContainer) childFocusRequestCallback ( + child elements.Focusable, +) ( + granted bool, +) { + if element.onFocusRequest != nil && element.onFocusRequest() { + element.Propagator.HandleUnfocus() + element.Propagator.HandleFocus(input.KeynavDirectionNeutral) + return true + } else { + return false + } +} + +func (element *DocumentContainer) doLayout () { + margin := element.theme.Margin(theme.PatternBackground) + padding := element.theme.Padding(theme.PatternBackground) + bounds := padding.Apply(element.Bounds()) + element.contentBounds = image.Rectangle { } + + dot := bounds.Min.Sub(element.scroll) + for index, entry := range element.children { + if index > 0 { + dot.Y += margin.Y + } + + width, height := entry.MinimumSize() + if width < bounds.Dx() { + width = bounds.Dx() + } + if typedChild, ok := entry.Element.(elements.Flexible); ok { + height = typedChild.FlexibleHeightFor(width) + } + + entry.Bounds.Min = dot + entry.Bounds.Max = image.Pt(dot.X + width, dot.Y + height) + element.children[index] = entry + element.contentBounds = element.contentBounds.Union(entry.Bounds) + dot.Y += height + } +} diff --git a/elements/basic/scrollcontainer.go b/elements/basic/scrollcontainer.go index 2d5cf20..b990f1d 100644 --- a/elements/basic/scrollcontainer.go +++ b/elements/basic/scrollcontainer.go @@ -267,7 +267,6 @@ func (element *ScrollContainer) layout () ( } func (element *ScrollContainer) draw () { - // XOR if element.horizontal != nil && element.vertical != nil { bounds := element.Bounds() bounds.Min = image.Pt ( diff --git a/examples/documentContainer/main.go b/examples/documentContainer/main.go new file mode 100644 index 0000000..3035399 --- /dev/null +++ b/examples/documentContainer/main.go @@ -0,0 +1,41 @@ +package main + +import "git.tebibyte.media/sashakoshka/tomo" +import "git.tebibyte.media/sashakoshka/tomo/elements/basic" +import _ "git.tebibyte.media/sashakoshka/tomo/backends/x" + +func main () { + tomo.Run(run) +} + +func run () { + window, _ := tomo.NewWindow(480, 360) + window.SetTitle("Scroll") + + scrollContainer := basicElements.NewScrollContainer(false, true) + document := basicElements.NewDocumentContainer() + + document.Adopt (basicElements.NewLabel ( + "A document container is a vertically stacked container " + + "capable of properly laying out flexible elements such as " + + "text-wrapped labels.", true)) + document.Adopt (basicElements.NewButton ( + "You can also include normal elements like buttons,")) + document.Adopt (basicElements.NewCheckbox ( + "checkboxes,", true)) + document.Adopt(basicElements.NewTextBox("", "And text boxes.")) + document.Adopt (basicElements.NewSpacer(true)) + document.Adopt (basicElements.NewLabel ( + "Document containers are meant to be placed inside of a " + + "ScrollContainer, like this one.", true)) + document.Adopt (basicElements.NewLabel ( + "You could use document containers to do things like display various " + + "forms of hypertext (like HTML, gemtext, markdown, etc.), " + + "lay out a settings menu with descriptive label text between " + + "control groups like in iOS, or list comment or chat histories.", true)) + + scrollContainer.Adopt(document) + window.Adopt(scrollContainer) + window.OnClose(tomo.Stop) + window.Show() +} From 15fa3b2497c70c5b09d865c676db86529cb30fb8 Mon Sep 17 00:00:00 2001 From: Sasha Koshka Date: Sat, 11 Mar 2023 18:27:16 -0500 Subject: [PATCH 04/17] Quelled some of the strangeness --- elements/basic/documentContainer.go | 14 ++++++++++++++ examples/documentContainer/main.go | 10 ++++++++++ textdraw/setter.go | 3 +-- 3 files changed, 25 insertions(+), 2 deletions(-) diff --git a/elements/basic/documentContainer.go b/elements/basic/documentContainer.go index c525cde..7e830b3 100644 --- a/elements/basic/documentContainer.go +++ b/elements/basic/documentContainer.go @@ -267,6 +267,10 @@ func (element *DocumentContainer) ScrollTo (position image.Point) { if position.Y < 0 { position.Y = 0 } + maxScrollHeight := element.maxScrollHeight() + if position.Y > maxScrollHeight { + position.Y = maxScrollHeight + } element.scroll = position if element.core.HasImage() && !element.warping { element.redoAll() @@ -274,6 +278,14 @@ func (element *DocumentContainer) ScrollTo (position image.Point) { } } +func (element *DocumentContainer) maxScrollHeight () (height int) { + padding := element.theme.Padding(theme.PatternSunken) + viewportHeight := element.Bounds().Dy() - padding.Vertical() + height = element.contentBounds.Dy() - viewportHeight + if height < 0 { height = 0 } + return +} + // ScrollAxes returns the supported axes for scrolling. func (element *DocumentContainer) ScrollAxes () (horizontal, vertical bool) { return false, true @@ -339,4 +351,6 @@ func (element *DocumentContainer) doLayout () { element.contentBounds = element.contentBounds.Union(entry.Bounds) dot.Y += height } + element.contentBounds = + element.contentBounds.Sub(element.contentBounds.Min) } diff --git a/examples/documentContainer/main.go b/examples/documentContainer/main.go index 3035399..9c92c3e 100644 --- a/examples/documentContainer/main.go +++ b/examples/documentContainer/main.go @@ -21,6 +21,16 @@ func run () { "text-wrapped labels.", true)) document.Adopt (basicElements.NewButton ( "You can also include normal elements like buttons,")) + document.Adopt (basicElements.NewButton ( + "You can also include normal elements like buttons,")) + document.Adopt (basicElements.NewButton ( + "You can also include normal elements like buttons,")) + document.Adopt (basicElements.NewButton ( + "You can also include normal elements like buttons,")) + document.Adopt (basicElements.NewButton ( + "You can also include normal elements like buttons,")) + document.Adopt (basicElements.NewButton ( + "You can also include normal elements like buttons,")) document.Adopt (basicElements.NewCheckbox ( "checkboxes,", true)) document.Adopt(basicElements.NewTextBox("", "And text boxes.")) diff --git a/textdraw/setter.go b/textdraw/setter.go index 83b9f90..b43da89 100644 --- a/textdraw/setter.go +++ b/textdraw/setter.go @@ -41,8 +41,7 @@ func (setter *TypeSetter) needLayout () { metrics := setter.face.Metrics() remaining := setter.text y := fixed.Int26_6(0) - maxY := fixed.I(setter.maxHeight) + metrics.Height - for len(remaining) > 0 && (y < maxY || setter.maxHeight == 0) { + for len(remaining) > 0 { // process one line line, remainingFromLine := DoLine ( remaining, setter.face, fixed.I(setter.maxWidth)) From b7a780037030a5f632948f1c62c4b5e6ca222499 Mon Sep 17 00:00:00 2001 From: Sasha Koshka Date: Sat, 11 Mar 2023 19:25:35 -0500 Subject: [PATCH 05/17] DocumentContainer has a proper minimum width --- elements/basic/documentContainer.go | 13 +++++++++---- examples/documentContainer/main.go | 29 +++++++++++++++-------------- 2 files changed, 24 insertions(+), 18 deletions(-) diff --git a/elements/basic/documentContainer.go b/elements/basic/documentContainer.go index 7e830b3..46b7271 100644 --- a/elements/basic/documentContainer.go +++ b/elements/basic/documentContainer.go @@ -222,9 +222,6 @@ func (element *DocumentContainer) SetTheme (new theme.Theme) { if new == element.theme.Theme { return } element.theme.Theme = new element.Propagator.SetTheme(new) - element.core.SetMinimumSize ( - element.theme.Padding(theme.PatternBackground).Horizontal(), - element.theme.Padding(theme.PatternBackground).Vertical(),) element.redoAll() } @@ -331,6 +328,7 @@ func (element *DocumentContainer) doLayout () { bounds := padding.Apply(element.Bounds()) element.contentBounds = image.Rectangle { } + minimumWidth := 0 dot := bounds.Min.Sub(element.scroll) for index, entry := range element.children { if index > 0 { @@ -338,6 +336,9 @@ func (element *DocumentContainer) doLayout () { } width, height := entry.MinimumSize() + if width > minimumWidth { + minimumWidth = width + } if width < bounds.Dx() { width = bounds.Dx() } @@ -351,6 +352,10 @@ func (element *DocumentContainer) doLayout () { element.contentBounds = element.contentBounds.Union(entry.Bounds) dot.Y += height } + element.contentBounds = - element.contentBounds.Sub(element.contentBounds.Min) + element.contentBounds.Sub(element.contentBounds.Min) + element.core.SetMinimumSize ( + minimumWidth + padding.Horizontal(), + padding.Vertical()) } diff --git a/examples/documentContainer/main.go b/examples/documentContainer/main.go index 9c92c3e..b14e9f8 100644 --- a/examples/documentContainer/main.go +++ b/examples/documentContainer/main.go @@ -1,5 +1,8 @@ package main +import "os" +import "image" +import _ "image/png" import "git.tebibyte.media/sashakoshka/tomo" import "git.tebibyte.media/sashakoshka/tomo/elements/basic" import _ "git.tebibyte.media/sashakoshka/tomo/backends/x" @@ -9,8 +12,14 @@ func main () { } func run () { - window, _ := tomo.NewWindow(480, 360) + window, _ := tomo.NewWindow(383, 360) window.SetTitle("Scroll") + + file, err := os.Open("assets/banner.png") + if err != nil { panic(err.Error()); return } + logo, _, err := image.Decode(file) + file.Close() + if err != nil { panic(err.Error()); return } scrollContainer := basicElements.NewScrollContainer(false, true) document := basicElements.NewDocumentContainer() @@ -18,21 +27,12 @@ func run () { document.Adopt (basicElements.NewLabel ( "A document container is a vertically stacked container " + "capable of properly laying out flexible elements such as " + - "text-wrapped labels.", true)) + "text-wrapped labels. You can also include normal elements " + + "like:", true)) document.Adopt (basicElements.NewButton ( - "You can also include normal elements like buttons,")) - document.Adopt (basicElements.NewButton ( - "You can also include normal elements like buttons,")) - document.Adopt (basicElements.NewButton ( - "You can also include normal elements like buttons,")) - document.Adopt (basicElements.NewButton ( - "You can also include normal elements like buttons,")) - document.Adopt (basicElements.NewButton ( - "You can also include normal elements like buttons,")) - document.Adopt (basicElements.NewButton ( - "You can also include normal elements like buttons,")) + "Buttons,")) document.Adopt (basicElements.NewCheckbox ( - "checkboxes,", true)) + "Checkboxes,", true)) document.Adopt(basicElements.NewTextBox("", "And text boxes.")) document.Adopt (basicElements.NewSpacer(true)) document.Adopt (basicElements.NewLabel ( @@ -43,6 +43,7 @@ func run () { "forms of hypertext (like HTML, gemtext, markdown, etc.), " + "lay out a settings menu with descriptive label text between " + "control groups like in iOS, or list comment or chat histories.", true)) + document.Adopt(basicElements.NewImage(logo)) scrollContainer.Adopt(document) window.Adopt(scrollContainer) From 5afbc0e713dd767291d71a61dd3c087e06fa630d Mon Sep 17 00:00:00 2001 From: Sasha Koshka Date: Sat, 11 Mar 2023 20:04:08 -0500 Subject: [PATCH 06/17] DocumentContainer constrains its scroll position on resize --- elements/basic/documentContainer.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/elements/basic/documentContainer.go b/elements/basic/documentContainer.go index 46b7271..e022cf2 100644 --- a/elements/basic/documentContainer.go +++ b/elements/basic/documentContainer.go @@ -186,6 +186,12 @@ func (element *DocumentContainer) redoAll () { // do a layout element.doLayout() + + maxScrollHeight := element.maxScrollHeight() + if element.scroll.Y > maxScrollHeight { + element.scroll.Y = maxScrollHeight + element.doLayout() + } // draw a background rocks := make([]image.Rectangle, len(element.children)) From 3d28ebe4cf7c30892bf3a29a89c959df118058d3 Mon Sep 17 00:00:00 2001 From: Sasha Koshka Date: Sun, 12 Mar 2023 00:17:35 -0500 Subject: [PATCH 07/17] Made interfacial changes that will allow for elements to be clipped --- elements/core/core.go | 6 ++++-- elements/element.go | 11 ++++++----- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/elements/core/core.go b/elements/core/core.go index 4b98dca..b5c6259 100644 --- a/elements/core/core.go +++ b/elements/core/core.go @@ -8,6 +8,7 @@ import "git.tebibyte.media/sashakoshka/tomo/canvas" // widgets. It is meant to be embedded directly into a struct. type Core struct { canvas canvas.Canvas + bounds image.Rectangle metrics struct { minimumWidth int @@ -37,7 +38,7 @@ func NewCore ( // overridden. func (core *Core) Bounds () (bounds image.Rectangle) { if core.canvas == nil { return } - return core.canvas.Bounds() + return core.bounds } // MinimumSize fulfils the tomo.Element interface. This should not need to be @@ -48,8 +49,9 @@ func (core *Core) MinimumSize () (width, height int) { // DrawTo fulfills the tomo.Element interface. This should not need to be // overridden. -func (core *Core) DrawTo (canvas canvas.Canvas) { +func (core *Core) DrawTo (canvas canvas.Canvas, bounds image.Rectangle) { core.canvas = canvas + core.bounds = bounds if core.drawSizeChange != nil && core.canvas != nil { core.drawSizeChange() } diff --git a/elements/element.go b/elements/element.go index 8881970..3df2466 100644 --- a/elements/element.go +++ b/elements/element.go @@ -9,13 +9,14 @@ import "git.tebibyte.media/sashakoshka/tomo/config" // Element represents a basic on-screen object. type Element interface { // Bounds reports the element's bounding box. This must reflect the - // bounding box of the last canvas given to the element by DrawTo. + // bounding last given to the element by DrawTo. Bounds () (bounds image.Rectangle) - // 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.Canvas) + // DrawTo gives the element a canvas to draw on, along with a bounding + // box to be used for laying out the element. This should only be called + // by the parent element. This is typically a region of the parent + // element's canvas. + DrawTo (canvas canvas.Canvas, bounds image.Rectangle) // OnDamage sets a function to be called when an area of the element is // drawn on and should be pushed to the screen. From 0f8affd2b20b99e256be5bd8cf9786a7e0bb4799 Mon Sep 17 00:00:00 2001 From: Sasha Koshka Date: Sun, 12 Mar 2023 01:04:06 -0500 Subject: [PATCH 08/17] Made similar changes to the Pattern interface and all of artist --- artist/pattern.go | 60 +++++++++++++++++--------------------- artist/patterns/border.go | 10 +++---- artist/patterns/texture.go | 17 +++++------ artist/patterns/uniform.go | 6 ++-- artist/shapes/ellipse.go | 23 ++++++++------- artist/shapes/rectangle.go | 46 +++++++++++++++++++---------- 6 files changed, 86 insertions(+), 76 deletions(-) diff --git a/artist/pattern.go b/artist/pattern.go index 558bbf6..6e43231 100644 --- a/artist/pattern.go +++ b/artist/pattern.go @@ -7,61 +7,55 @@ import "git.tebibyte.media/sashakoshka/tomo/shatter" // Pattern is capable of drawing to a canvas within the bounds of a given // clipping rectangle. type Pattern interface { - // Draw draws to destination, using the bounds of destination as a width - // and height for things like gradients, bevels, etc. The pattern may - // not draw outside the union of destination.Bounds() and clip. The - // clipping rectangle effectively takes a subset of the pattern. To - // change the bounds of the pattern itself, use canvas.Cut() on the - // destination before passing it to Draw(). - Draw (destination canvas.Canvas, clip image.Rectangle) + // Draw draws the pattern onto the destination canvas, using the + // specified bounds. The given bounds can be smaller or larger than the + // bounds of the destination canvas. The destination canvas can be cut + // using canvas.Cut() to draw only a specific subset of a pattern. + Draw (destination canvas.Canvas, bounds image.Rectangle) } -// Draw lets you use several clipping rectangles to draw a pattern. -func Draw ( +// Fill fills the destination canvas with the given pattern. +func Fill (destination canvas.Canvas, source Pattern) (updated image.Rectangle) { + source.Draw(destination, destination.Bounds()) + return destination.Bounds() +} + +// Draw lets you draw several subsets of +func DrawClip ( destination canvas.Canvas, source Pattern, - clips ...image.Rectangle, + bounds image.Rectangle, + subsets ...image.Rectangle, ) ( updatedRegion image.Rectangle, ) { - for _, clip := range clips { - source.Draw(destination, clip) - updatedRegion = updatedRegion.Union(clip) + for _, subset := range subsets { + source.Draw(canvas.Cut(destination, subset), bounds) + updatedRegion = updatedRegion.Union(subset) } return } -// DrawBounds lets you specify an overall bounding rectangle for drawing a -// pattern. The destination is cut to this rectangle. -func DrawBounds ( - destination canvas.Canvas, - source Pattern, - bounds image.Rectangle, -) ( - updatedRegion image.Rectangle, -) { - return Draw(canvas.Cut(destination, bounds), source, bounds) -} - -// DrawShatter is like an inverse of Draw, drawing nothing in the areas -// specified in "rocks". +// DrawShatter is like an inverse of DrawClip, drawing nothing in the areas +// specified by "rocks". func DrawShatter ( destination canvas.Canvas, source Pattern, + bounds image.Rectangle, rocks ...image.Rectangle, ) ( updatedRegion image.Rectangle, ) { - tiles := shatter.Shatter(destination.Bounds(), rocks...) - return Draw(destination, source, tiles...) + tiles := shatter.Shatter(bounds, rocks...) + return DrawClip(destination, source, bounds, tiles...) } // AllocateSample returns a new canvas containing the result of a pattern. The // resulting canvas can be sourced from shape drawing functions. I beg of you // please do not call this every time you need to draw a shape with a pattern on // it because that is horrible and cruel to the computer. -func AllocateSample (source Pattern, width, height int) (allocated canvas.Canvas) { - allocated = canvas.NewBasicCanvas(width, height) - source.Draw(allocated, allocated.Bounds()) - return +func AllocateSample (source Pattern, width, height int) canvas.Canvas { + allocated := canvas.NewBasicCanvas(width, height) + Fill(allocated, source) + return allocated } diff --git a/artist/patterns/border.go b/artist/patterns/border.go index 3202a0b..0ce56c0 100644 --- a/artist/patterns/border.go +++ b/artist/patterns/border.go @@ -37,9 +37,9 @@ type Border struct { // Draw draws the border pattern onto the destination canvas within the clipping // bounds. -func (pattern Border) Draw (destination canvas.Canvas, clip image.Rectangle) { - bounds := clip.Canon().Intersect(destination.Bounds()) - if bounds.Empty() { return } +func (pattern Border) Draw (destination canvas.Canvas, bounds image.Rectangle) { + drawBounds := bounds.Canon().Intersect(destination.Bounds()) + if drawBounds.Empty() { return } srcSections := nonasect(pattern.Bounds(), pattern.Inset) srcTextures := [9]Texture { } @@ -47,9 +47,9 @@ func (pattern Border) Draw (destination canvas.Canvas, clip image.Rectangle) { srcTextures[index].Canvas = canvas.Cut(pattern, section) } - dstSections := nonasect(destination.Bounds(), pattern.Inset) + dstSections := nonasect(bounds, pattern.Inset) for index, section := range dstSections { - srcTextures[index].Draw(canvas.Cut(destination, section), clip) + srcTextures[index].Draw(destination, section) } } diff --git a/artist/patterns/texture.go b/artist/patterns/texture.go index 47f0596..91e3e40 100644 --- a/artist/patterns/texture.go +++ b/artist/patterns/texture.go @@ -9,25 +9,24 @@ type Texture struct { canvas.Canvas } -// Draw tiles the pattern's canvas within the clipping bounds. The minimum +// Draw tiles the pattern's canvas within the given bounds. The minimum // points of the pattern's canvas and the destination canvas will be lined up. -func (pattern Texture) Draw (destination canvas.Canvas, clip image.Rectangle) { - realBounds := destination.Bounds() - bounds := clip.Canon().Intersect(realBounds) - if bounds.Empty() { return } +func (pattern Texture) Draw (destination canvas.Canvas, bounds image.Rectangle) { + drawBounds := bounds.Canon().Intersect(destination.Bounds()) + if drawBounds.Empty() { return } dstData, dstStride := destination.Buffer() srcData, srcStride := pattern.Buffer() srcBounds := pattern.Bounds() dstPoint := image.Point { } - srcPoint := bounds.Min.Sub(realBounds.Min).Add(srcBounds.Min) + srcPoint := drawBounds.Min.Sub(bounds.Min).Add(srcBounds.Min) srcPoint.X = wrap(srcPoint.X, srcBounds.Min.X, srcBounds.Max.X) srcPoint.Y = wrap(srcPoint.Y, srcBounds.Min.Y, srcBounds.Max.Y) - for dstPoint.Y = bounds.Min.Y; dstPoint.Y < bounds.Max.Y; dstPoint.Y ++ { + for dstPoint.Y = drawBounds.Min.Y; dstPoint.Y < drawBounds.Max.Y; dstPoint.Y ++ { srcPoint.X = srcBounds.Min.X - dstPoint.X = bounds.Min.X + dstPoint.X = drawBounds.Min.X dstYComponent := dstPoint.Y * dstStride srcYComponent := srcPoint.Y * srcStride @@ -42,7 +41,7 @@ func (pattern Texture) Draw (destination canvas.Canvas, clip image.Rectangle) { } dstPoint.X ++ - if dstPoint.X >= bounds.Max.X { + if dstPoint.X >= drawBounds.Max.X { break } } diff --git a/artist/patterns/uniform.go b/artist/patterns/uniform.go index 8efb8be..173367a 100644 --- a/artist/patterns/uniform.go +++ b/artist/patterns/uniform.go @@ -9,9 +9,9 @@ import "git.tebibyte.media/sashakoshka/tomo/artist/shapes" // Uniform is a pattern that draws a solid color. type Uniform color.RGBA -// Draw fills the clipping rectangle with the pattern's color. -func (pattern Uniform) Draw (destination canvas.Canvas, clip image.Rectangle) { - shapes.FillColorRectangle(destination, color.RGBA(pattern), clip) +// Draw fills the bounding rectangle with the pattern's color. +func (pattern Uniform) Draw (destination canvas.Canvas, bounds image.Rectangle) { + shapes.FillColorRectangle(destination, color.RGBA(pattern), bounds) } // Uhex creates a new Uniform pattern from an RGBA integer value. diff --git a/artist/shapes/ellipse.go b/artist/shapes/ellipse.go index 0e5f0c5..c750e6f 100644 --- a/artist/shapes/ellipse.go +++ b/artist/shapes/ellipse.go @@ -13,6 +13,7 @@ import "git.tebibyte.media/sashakoshka/tomo/canvas" func FillEllipse ( destination canvas.Canvas, source canvas.Canvas, + bounds image.Rectangle, ) ( updatedRegion image.Rectangle, ) { @@ -20,15 +21,17 @@ func FillEllipse ( srcData, srcStride := source.Buffer() offset := source.Bounds().Min.Sub(destination.Bounds().Min) - bounds := source.Bounds().Sub(offset).Intersect(destination.Bounds()) - realBounds := destination.Bounds() + drawBounds := + source.Bounds().Sub(offset). + Intersect(destination.Bounds()). + Intersect(bounds) if bounds.Empty() { return } updatedRegion = bounds point := image.Point { } - for point.Y = bounds.Min.Y; point.Y < bounds.Max.Y; point.Y ++ { - for point.X = bounds.Min.X; point.X < bounds.Max.X; point.X ++ { - if inEllipse(point, realBounds) { + for point.Y = drawBounds.Min.Y; point.Y < drawBounds.Max.Y; point.Y ++ { + for point.X = drawBounds.Min.X; point.X < drawBounds.Max.X; point.X ++ { + if inEllipse(point, bounds) { offsetPoint := point.Add(offset) dstIndex := point.X + point.Y * dstStride srcIndex := offsetPoint.X + offsetPoint.Y * srcStride @@ -41,6 +44,7 @@ func FillEllipse ( func StrokeEllipse ( destination canvas.Canvas, source canvas.Canvas, + bounds image.Rectangle, weight int, ) { if weight < 1 { return } @@ -48,10 +52,9 @@ func StrokeEllipse ( dstData, dstStride := destination.Buffer() srcData, srcStride := source.Buffer() - bounds := destination.Bounds().Inset(weight - 1) + drawBounds := destination.Bounds().Inset(weight - 1) offset := source.Bounds().Min.Sub(destination.Bounds().Min) - realBounds := destination.Bounds() - if bounds.Empty() { return } + if drawBounds.Empty() { return } context := ellipsePlottingContext { plottingContext: plottingContext { @@ -61,9 +64,9 @@ func StrokeEllipse ( srcStride: srcStride, weight: weight, offset: offset, - bounds: realBounds, + bounds: bounds, }, - radii: image.Pt(bounds.Dx() / 2, bounds.Dy() / 2), + radii: image.Pt(drawBounds.Dx() / 2, drawBounds.Dy() / 2), } context.center = bounds.Min.Add(context.radii) context.plotEllipse() diff --git a/artist/shapes/rectangle.go b/artist/shapes/rectangle.go index 8912ade..b52ae68 100644 --- a/artist/shapes/rectangle.go +++ b/artist/shapes/rectangle.go @@ -10,20 +10,24 @@ import "git.tebibyte.media/sashakoshka/tomo/shatter" func FillRectangle ( destination canvas.Canvas, source canvas.Canvas, + bounds image.Rectangle, ) ( updatedRegion image.Rectangle, ) { dstData, dstStride := destination.Buffer() srcData, srcStride := source.Buffer() - offset := source.Bounds().Min.Sub(destination.Bounds().Min) - bounds := source.Bounds().Sub(offset).Intersect(destination.Bounds()) - if bounds.Empty() { return } - updatedRegion = bounds + offset := source.Bounds().Min.Sub(destination.Bounds().Min) + drawBounds := + source.Bounds().Sub(offset). + Intersect(destination.Bounds()). + Intersect(bounds) + if drawBounds.Empty() { return } + updatedRegion = drawBounds point := image.Point { } - for point.Y = bounds.Min.Y; point.Y < bounds.Max.Y; point.Y ++ { - for point.X = bounds.Min.X; point.X < bounds.Max.X; point.X ++ { + for point.Y = drawBounds.Min.Y; point.Y < drawBounds.Max.Y; point.Y ++ { + for point.X = drawBounds.Min.X; point.X < drawBounds.Max.X; point.X ++ { offsetPoint := point.Add(offset) dstIndex := point.X + point.Y * dstStride srcIndex := offsetPoint.X + offsetPoint.Y * srcStride @@ -36,15 +40,16 @@ func FillRectangle ( func StrokeRectangle ( destination canvas.Canvas, source canvas.Canvas, + bounds image.Rectangle, weight int, +) ( + updatedRegion image.Rectangle, ) { - bounds := destination.Bounds() insetBounds := bounds.Inset(weight) if insetBounds.Empty() { - FillRectangle(destination, source) - return + return FillRectangle(destination, source, bounds) } - FillRectangleShatter(destination, source, insetBounds) + return FillRectangleShatter(destination, source, insetBounds) } // FillRectangleShatter is like FillRectangle, but it does not draw in areas @@ -52,15 +57,19 @@ func StrokeRectangle ( func FillRectangleShatter ( destination canvas.Canvas, source canvas.Canvas, + bounds image.Rectangle, rocks ...image.Rectangle, +) ( + updatedRegion image.Rectangle, ) { - tiles := shatter.Shatter(destination.Bounds(), rocks...) - offset := source.Bounds().Min.Sub(destination.Bounds().Min) + tiles := shatter.Shatter(bounds, rocks...) for _, tile := range tiles { FillRectangle ( canvas.Cut(destination, tile), - canvas.Cut(source, tile.Add(offset))) + source, tile) + updatedRegion = updatedRegion.Union(tile) } + return } // FillColorRectangle fills a rectangle within the destination canvas with a @@ -92,11 +101,15 @@ func FillColorRectangleShatter ( color color.RGBA, bounds image.Rectangle, rocks ...image.Rectangle, +) ( + updatedRegion image.Rectangle, ) { tiles := shatter.Shatter(bounds, rocks...) for _, tile := range tiles { FillColorRectangle(destination, color, tile) + updatedRegion = updatedRegion.Union(tile) } + return } // StrokeColorRectangle is similar to FillColorRectangle, but it draws an inset @@ -106,11 +119,12 @@ func StrokeColorRectangle ( color color.RGBA, bounds image.Rectangle, weight int, +) ( + updatedRegion image.Rectangle, ) { insetBounds := bounds.Inset(weight) if insetBounds.Empty() { - FillColorRectangle(destination, color, bounds) - return + return FillColorRectangle(destination, color, bounds) } - FillColorRectangleShatter(destination, color, bounds, insetBounds) + return FillColorRectangleShatter(destination, color, bounds, insetBounds) } From d31aee1ba884176ad7a2146722c9a28644072057 Mon Sep 17 00:00:00 2001 From: Sasha Koshka Date: Sun, 12 Mar 2023 01:06:12 -0500 Subject: [PATCH 09/17] X backend now follows API --- backends/x/window.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backends/x/window.go b/backends/x/window.go index f3981cb..67d352a 100644 --- a/backends/x/window.go +++ b/backends/x/window.go @@ -257,7 +257,7 @@ func (window *Window) redrawChildEntirely () { func (window *Window) resizeChildToFit () { window.skipChildDrawCallback = true - window.child.DrawTo(window.canvas) + window.child.DrawTo(window.canvas, window.canvas.Bounds()) window.skipChildDrawCallback = false } From 92e5822185e2dbc7af7d1df5920f9114c817a71b Mon Sep 17 00:00:00 2001 From: Sasha Koshka Date: Sun, 12 Mar 2023 01:15:36 -0500 Subject: [PATCH 10/17] Basic and fun elements conform to new API change --- elements/basic/checkbox.go | 2 +- elements/basic/container.go | 11 ++++++----- elements/basic/documentContainer.go | 11 ++++++----- elements/basic/listentry.go | 2 +- elements/basic/progressbar.go | 2 +- elements/basic/scrollbar.go | 6 ++---- elements/basic/scrollcontainer.go | 21 ++++++++++++++------- elements/basic/slider.go | 7 ++----- elements/basic/switch.go | 5 ++--- elements/fun/piano.go | 4 ++-- 10 files changed, 37 insertions(+), 34 deletions(-) diff --git a/elements/basic/checkbox.go b/elements/basic/checkbox.go index ab2217e..672515b 100644 --- a/elements/basic/checkbox.go +++ b/elements/basic/checkbox.go @@ -176,7 +176,7 @@ func (element *Checkbox) draw () { backgroundPattern.Draw(element.core, bounds) pattern := element.theme.Pattern(theme.PatternButton, state) - artist.DrawBounds(element.core, pattern, boxBounds) + pattern.Draw(element.core, boxBounds) textBounds := element.drawer.LayoutBounds() margin := element.theme.Margin(theme.PatternBackground) diff --git a/elements/basic/container.go b/elements/basic/container.go index 40f215d..841628a 100644 --- a/elements/basic/container.go +++ b/elements/basic/container.go @@ -138,7 +138,7 @@ func (element *Container) Disown (child elements.Element) { } func (element *Container) clearChildEventHandlers (child elements.Element) { - child.DrawTo(nil) + child.DrawTo(nil, image.Rectangle { }) child.OnDamage(nil) child.OnMinimumSizeChange(nil) if child0, ok := child.(elements.Focusable); ok { @@ -203,7 +203,7 @@ func (element *Container) redoAll () { // remove child canvasses so that any operations done in here will not // cause a child to draw to a wack ass canvas. for _, entry := range element.children { - entry.DrawTo(nil) + entry.DrawTo(nil, entry.Bounds) } // do a layout @@ -217,12 +217,13 @@ func (element *Container) redoAll () { pattern := element.theme.Pattern ( theme.PatternBackground, theme.State { }) - artist.DrawShatter ( - element.core, pattern, rocks...) + artist.DrawShatter(element.core, pattern, element.Bounds(), rocks...) // cut our canvas up and give peices to child elements for _, entry := range element.children { - entry.DrawTo(canvas.Cut(element.core, entry.Bounds)) + entry.DrawTo ( + canvas.Cut(element.core, entry.Bounds), + entry.Bounds) } } diff --git a/elements/basic/documentContainer.go b/elements/basic/documentContainer.go index e022cf2..81e8c60 100644 --- a/elements/basic/documentContainer.go +++ b/elements/basic/documentContainer.go @@ -123,7 +123,7 @@ func (element *DocumentContainer) Disown (child elements.Element) { } func (element *DocumentContainer) clearChildEventHandlers (child elements.Element) { - child.DrawTo(nil) + child.DrawTo(nil, image.Rectangle { }) child.OnDamage(nil) child.OnMinimumSizeChange(nil) if child0, ok := child.(elements.Focusable); ok { @@ -201,8 +201,7 @@ func (element *DocumentContainer) redoAll () { pattern := element.theme.Pattern ( theme.PatternBackground, theme.State { }) - artist.DrawShatter ( - element.core, pattern, rocks...) + artist.DrawShatter(element.core, pattern, element.Bounds(), rocks...) element.partition() if element.onScrollBoundsChange != nil { @@ -212,13 +211,15 @@ func (element *DocumentContainer) redoAll () { func (element *DocumentContainer) partition () { for _, entry := range element.children { - entry.DrawTo(nil) + entry.DrawTo(nil, entry.Bounds) } // cut our canvas up and give peices to child elements for _, entry := range element.children { if entry.Bounds.Overlaps(element.Bounds()) { - entry.DrawTo(canvas.Cut(element.core, entry.Bounds)) + entry.DrawTo ( + canvas.Cut(element.core, entry.Bounds), + entry.Bounds) } } } diff --git a/elements/basic/listentry.go b/elements/basic/listentry.go index d1020c2..2d5267a 100644 --- a/elements/basic/listentry.go +++ b/elements/basic/listentry.go @@ -70,7 +70,7 @@ func (entry *ListEntry) Draw ( pattern := entry.theme.Pattern(theme.PatternRaised, state) padding := entry.theme.Padding(theme.PatternRaised) bounds := entry.Bounds().Add(offset) - artist.DrawBounds(destination, pattern, bounds) + pattern.Draw(destination, bounds) foreground := entry.theme.Color (theme.ColorForeground, state) return entry.drawer.Draw ( diff --git a/elements/basic/progressbar.go b/elements/basic/progressbar.go index 8e5ce32..2645cef 100644 --- a/elements/basic/progressbar.go +++ b/elements/basic/progressbar.go @@ -78,5 +78,5 @@ func (element *ProgressBar) draw () { bounds.Min.X + int(float64(bounds.Dx()) * element.progress), bounds.Max.Y) mercury := element.theme.Pattern(theme.PatternMercury, theme.State { }) - artist.DrawBounds(element.core, mercury, meterBounds) + mercury.Draw(element.core, meterBounds) } diff --git a/elements/basic/scrollbar.go b/elements/basic/scrollbar.go index 3c2c16d..1c51449 100644 --- a/elements/basic/scrollbar.go +++ b/elements/basic/scrollbar.go @@ -315,12 +315,10 @@ func (element *ScrollBar) draw () { Disabled: !element.Enabled(), Pressed: element.dragging, } - artist.DrawBounds ( + element.theme.Pattern(theme.PatternGutter, state).Draw ( element.core, - element.theme.Pattern(theme.PatternGutter, state), bounds) - artist.DrawBounds ( + element.theme.Pattern(theme.PatternHandle, state).Draw ( element.core, - element.theme.Pattern(theme.PatternHandle, state), element.bar) } diff --git a/elements/basic/scrollcontainer.go b/elements/basic/scrollcontainer.go index b990f1d..e333dbb 100644 --- a/elements/basic/scrollcontainer.go +++ b/elements/basic/scrollcontainer.go @@ -120,7 +120,7 @@ func (element *ScrollContainer) setChildEventHandlers (child elements.Element) { } func (element *ScrollContainer) clearChildEventHandlers (child elements.Scrollable) { - child.DrawTo(nil) + child.DrawTo(nil, image.Rectangle { }) child.OnDamage(nil) child.OnMinimumSizeChange(nil) child.OnScrollBoundsChange(nil) @@ -198,19 +198,26 @@ func (element *ScrollContainer) Child (index int) (child elements.Element) { func (element *ScrollContainer) redoAll () { if !element.core.HasImage() { return } - if element.child != nil { element.child.DrawTo(nil) } - if element.horizontal != nil { element.horizontal.DrawTo(nil) } - if element.vertical != nil { element.vertical.DrawTo(nil) } + zr := image.Rectangle { } + if element.child != nil { element.child.DrawTo(nil, zr) } + if element.horizontal != nil { element.horizontal.DrawTo(nil, zr) } + if element.vertical != nil { element.vertical.DrawTo(nil, zr) } childBounds, horizontalBounds, verticalBounds := element.layout() if element.child != nil { - element.child.DrawTo(canvas.Cut(element.core, childBounds)) + element.child.DrawTo ( + canvas.Cut(element.core, childBounds), + childBounds) } if element.horizontal != nil { - element.horizontal.DrawTo(canvas.Cut(element.core, horizontalBounds)) + element.horizontal.DrawTo ( + canvas.Cut(element.core, horizontalBounds), + horizontalBounds) } if element.vertical != nil { - element.vertical.DrawTo(canvas.Cut(element.core, verticalBounds)) + element.vertical.DrawTo ( + canvas.Cut(element.core, verticalBounds), + verticalBounds) } element.draw() } diff --git a/elements/basic/slider.go b/elements/basic/slider.go index 67b6f65..6ad8ff1 100644 --- a/elements/basic/slider.go +++ b/elements/basic/slider.go @@ -4,7 +4,6 @@ import "image" import "git.tebibyte.media/sashakoshka/tomo/input" import "git.tebibyte.media/sashakoshka/tomo/theme" import "git.tebibyte.media/sashakoshka/tomo/config" -import "git.tebibyte.media/sashakoshka/tomo/artist" import "git.tebibyte.media/sashakoshka/tomo/elements/core" // Slider is a slider control with a floating point value between zero and one. @@ -229,12 +228,10 @@ func (element *Slider) draw () { Disabled: !element.Enabled(), Pressed: element.dragging, } - artist.DrawBounds ( + element.theme.Pattern(theme.PatternGutter, state).Draw ( element.core, - element.theme.Pattern(theme.PatternGutter, state), bounds) - artist.DrawBounds ( + element.theme.Pattern(theme.PatternHandle, state).Draw ( element.core, - element.theme.Pattern(theme.PatternHandle, state), element.bar) } diff --git a/elements/basic/switch.go b/elements/basic/switch.go index 4eeb9ad..80491de 100644 --- a/elements/basic/switch.go +++ b/elements/basic/switch.go @@ -4,7 +4,6 @@ import "image" import "git.tebibyte.media/sashakoshka/tomo/input" import "git.tebibyte.media/sashakoshka/tomo/theme" import "git.tebibyte.media/sashakoshka/tomo/config" -import "git.tebibyte.media/sashakoshka/tomo/artist" import "git.tebibyte.media/sashakoshka/tomo/textdraw" import "git.tebibyte.media/sashakoshka/tomo/elements/core" @@ -185,11 +184,11 @@ func (element *Switch) draw () { gutterPattern := element.theme.Pattern ( theme.PatternGutter, state) - artist.DrawBounds(element.core, gutterPattern, gutterBounds) + gutterPattern.Draw(element.core, gutterBounds) handlePattern := element.theme.Pattern ( theme.PatternHandle, state) - artist.DrawBounds(element.core, handlePattern, handleBounds) + handlePattern.Draw(element.core, handleBounds) textBounds := element.drawer.LayoutBounds() offset := bounds.Min.Add(image.Point { diff --git a/elements/fun/piano.go b/elements/fun/piano.go index 4cba308..0f01bd3 100644 --- a/elements/fun/piano.go +++ b/elements/fun/piano.go @@ -316,7 +316,7 @@ func (element *Piano) drawFlat ( state.Pressed = pressed pattern := element.theme.Theme.Pattern ( theme.PatternButton, state, theme.C("fun", "flatKey")) - artist.DrawBounds(element.core, pattern, bounds) + pattern.Draw(element.core, bounds) } func (element *Piano) drawSharp ( @@ -327,5 +327,5 @@ func (element *Piano) drawSharp ( state.Pressed = pressed pattern := element.theme.Theme.Pattern ( theme.PatternButton, state, theme.C("fun", "sharpKey")) - artist.DrawBounds(element.core, pattern, bounds) + pattern.Draw(element.core, bounds) } From c45268d8c1031f06e5891b106ccd348684372c74 Mon Sep 17 00:00:00 2001 From: Sasha Koshka Date: Sun, 12 Mar 2023 01:19:40 -0500 Subject: [PATCH 11/17] Testing elements now conform to the new API --- elements/testing/artist.go | 24 +++++++++++------------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/elements/testing/artist.go b/elements/testing/artist.go index bb42bfa..fe5c162 100644 --- a/elements/testing/artist.go +++ b/elements/testing/artist.go @@ -79,34 +79,32 @@ func (element *Artist) draw () { } tiles := shatter.Shatter(c41.Bounds(), rocks...) for index, tile := range tiles { - artist.DrawBounds ( - element.core, - []artist.Pattern { - patterns.Uhex(0xFF0000FF), - patterns.Uhex(0x00FF00FF), - patterns.Uhex(0xFF00FFFF), - patterns.Uhex(0xFFFF00FF), - patterns.Uhex(0x00FFFFFF), - } [index % 5], tile) + []artist.Pattern { + patterns.Uhex(0xFF0000FF), + patterns.Uhex(0x00FF00FF), + patterns.Uhex(0xFF00FFFF), + patterns.Uhex(0xFFFF00FF), + patterns.Uhex(0x00FFFFFF), + } [index % 5].Draw(element.core, tile) } // 0, 2 c02 := element.cellAt(0, 2) shapes.StrokeColorRectangle(c02, artist.Hex(0x888888FF), c02.Bounds(), 1) - shapes.FillEllipse(c02, c41) + shapes.FillEllipse(c02, c41, c02.Bounds()) // 1, 2 c12 := element.cellAt(1, 2) shapes.StrokeColorRectangle(c12, artist.Hex(0x888888FF), c12.Bounds(), 1) - shapes.StrokeEllipse(c12, c41, 5) + shapes.StrokeEllipse(c12, c41, c12.Bounds(), 5) // 2, 2 c22 := element.cellAt(2, 2) - shapes.FillRectangle(c22, c41) + shapes.FillRectangle(c22, c41, c22.Bounds()) // 3, 2 c32 := element.cellAt(3, 2) - shapes.StrokeRectangle(c32, c41, 5) + shapes.StrokeRectangle(c32, c41, c32.Bounds(), 5) // 4, 2 c42 := element.cellAt(4, 2) From be45f7ad718075cbe1792a9c97c39aa1a57bfd21 Mon Sep 17 00:00:00 2001 From: Sasha Koshka Date: Sun, 12 Mar 2023 01:23:20 -0500 Subject: [PATCH 12/17] Fixed some artist bugs --- artist/shapes/ellipse.go | 2 +- artist/shapes/rectangle.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/artist/shapes/ellipse.go b/artist/shapes/ellipse.go index c750e6f..c30041d 100644 --- a/artist/shapes/ellipse.go +++ b/artist/shapes/ellipse.go @@ -68,7 +68,7 @@ func StrokeEllipse ( }, radii: image.Pt(drawBounds.Dx() / 2, drawBounds.Dy() / 2), } - context.center = bounds.Min.Add(context.radii) + context.center = drawBounds.Min.Add(context.radii) context.plotEllipse() } diff --git a/artist/shapes/rectangle.go b/artist/shapes/rectangle.go index b52ae68..968e00c 100644 --- a/artist/shapes/rectangle.go +++ b/artist/shapes/rectangle.go @@ -49,7 +49,7 @@ func StrokeRectangle ( if insetBounds.Empty() { return FillRectangle(destination, source, bounds) } - return FillRectangleShatter(destination, source, insetBounds) + return FillRectangleShatter(destination, source, bounds, insetBounds) } // FillRectangleShatter is like FillRectangle, but it does not draw in areas From 37048c675901aec37268d6ec821777f8604b929c Mon Sep 17 00:00:00 2001 From: Sasha Koshka Date: Sun, 12 Mar 2023 01:33:05 -0500 Subject: [PATCH 13/17] Raycaster runs? --- elements/basic/checkbox.go | 1 - elements/basic/progressbar.go | 1 - elements/basic/scrollbar.go | 1 - examples/raycaster/game.go | 10 +++++++--- 4 files changed, 7 insertions(+), 6 deletions(-) diff --git a/elements/basic/checkbox.go b/elements/basic/checkbox.go index 672515b..14690bb 100644 --- a/elements/basic/checkbox.go +++ b/elements/basic/checkbox.go @@ -4,7 +4,6 @@ import "image" import "git.tebibyte.media/sashakoshka/tomo/input" import "git.tebibyte.media/sashakoshka/tomo/theme" import "git.tebibyte.media/sashakoshka/tomo/config" -import "git.tebibyte.media/sashakoshka/tomo/artist" import "git.tebibyte.media/sashakoshka/tomo/textdraw" import "git.tebibyte.media/sashakoshka/tomo/elements/core" diff --git a/elements/basic/progressbar.go b/elements/basic/progressbar.go index 2645cef..9178e3e 100644 --- a/elements/basic/progressbar.go +++ b/elements/basic/progressbar.go @@ -3,7 +3,6 @@ package basicElements import "image" import "git.tebibyte.media/sashakoshka/tomo/theme" import "git.tebibyte.media/sashakoshka/tomo/config" -import "git.tebibyte.media/sashakoshka/tomo/artist" import "git.tebibyte.media/sashakoshka/tomo/elements/core" // ProgressBar displays a visual indication of how far along a task is. diff --git a/elements/basic/scrollbar.go b/elements/basic/scrollbar.go index 1c51449..4f9af08 100644 --- a/elements/basic/scrollbar.go +++ b/elements/basic/scrollbar.go @@ -4,7 +4,6 @@ import "image" import "git.tebibyte.media/sashakoshka/tomo/input" import "git.tebibyte.media/sashakoshka/tomo/theme" import "git.tebibyte.media/sashakoshka/tomo/config" -import "git.tebibyte.media/sashakoshka/tomo/artist" import "git.tebibyte.media/sashakoshka/tomo/elements/core" // ScrollBar is an element similar to Slider, but it has special behavior that diff --git a/examples/raycaster/game.go b/examples/raycaster/game.go index 25a05c4..e0413c1 100644 --- a/examples/raycaster/game.go +++ b/examples/raycaster/game.go @@ -1,6 +1,7 @@ package main import "time" +import "image" import "git.tebibyte.media/sashakoshka/tomo" import "git.tebibyte.media/sashakoshka/tomo/canvas" @@ -30,14 +31,17 @@ func NewGame (world World, textures Textures) (game *Game) { return } -func (game *Game) DrawTo (canvas canvas.Canvas) { +func (game *Game) DrawTo (canvas canvas.Canvas, bounds image.Rectangle) { if canvas == nil { - game.stopChan <- true + select { + case game.stopChan <- true: + default: + } } else if !game.running { game.running = true go game.run() } - game.Raycaster.DrawTo(canvas) + game.Raycaster.DrawTo(canvas, bounds) } func (game *Game) Stamina () float64 { From b09994973c900d39b38c2f20bdb3dab158a57add Mon Sep 17 00:00:00 2001 From: Sasha Koshka Date: Sun, 12 Mar 2023 01:47:58 -0500 Subject: [PATCH 14/17] List and Piano do shattering properly --- artist/pattern.go | 2 +- elements/basic/list.go | 2 +- elements/fun/piano.go | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/artist/pattern.go b/artist/pattern.go index 6e43231..c2caa5c 100644 --- a/artist/pattern.go +++ b/artist/pattern.go @@ -20,7 +20,7 @@ func Fill (destination canvas.Canvas, source Pattern) (updated image.Rectangle) return destination.Bounds() } -// Draw lets you draw several subsets of +// DrawClip lets you draw several subsets of a pattern at once. func DrawClip ( destination canvas.Canvas, source Pattern, diff --git a/elements/basic/list.go b/elements/basic/list.go index f6386b1..0f4c009 100644 --- a/elements/basic/list.go +++ b/elements/basic/list.go @@ -461,5 +461,5 @@ func (element *List) draw () { ).Add(innerBounds.Min).Intersect(innerBounds) pattern := element.theme.Pattern(theme.PatternSunken, state) artist.DrawShatter ( - element.core, pattern, covered) + element.core, pattern, bounds, covered) } diff --git a/elements/fun/piano.go b/elements/fun/piano.go index 0f01bd3..e18cac8 100644 --- a/elements/fun/piano.go +++ b/elements/fun/piano.go @@ -305,7 +305,7 @@ func (element *Piano) draw () { pattern := element.theme.Pattern(theme.PatternPinboard, state) artist.DrawShatter ( - element.core, pattern, element.contentBounds) + element.core, pattern, element.Bounds(), element.contentBounds) } func (element *Piano) drawFlat ( From 7ef95cc751cc2fd04be57bb625b0a4d4cc0e3658 Mon Sep 17 00:00:00 2001 From: Sasha Koshka Date: Sun, 12 Mar 2023 01:57:56 -0500 Subject: [PATCH 15/17] Removed unneeded Container.reflectChildProperties() --- elements/basic/container.go | 17 ----------------- elements/core/selectable.go | 2 ++ 2 files changed, 2 insertions(+), 17 deletions(-) diff --git a/elements/basic/container.go b/elements/basic/container.go index 841628a..ec8a024 100644 --- a/elements/basic/container.go +++ b/elements/basic/container.go @@ -87,7 +87,6 @@ func (element *Container) Adopt (child elements.Element, expand bool) { // refresh stale data element.updateMinimumSize() - element.reflectChildProperties() if element.core.HasImage() && !element.warping { element.redoAll() element.core.DamageAll() @@ -130,7 +129,6 @@ func (element *Container) Disown (child elements.Element) { } element.updateMinimumSize() - element.reflectChildProperties() if element.core.HasImage() && !element.warping { element.redoAll() element.core.DamageAll() @@ -158,7 +156,6 @@ func (element *Container) DisownAll () { element.children = nil element.updateMinimumSize() - element.reflectChildProperties() if element.core.HasImage() && !element.warping { element.redoAll() element.core.DamageAll() @@ -256,20 +253,6 @@ func (element *Container) OnFocusMotionRequest ( element.Propagator.OnFocusMotionRequest(callback) } -func (element *Container) reflectChildProperties () { - focusable := false - for _, entry := range element.children { - _, focusable := entry.Element.(elements.Focusable) - if focusable { - focusable = true - break - } - } - if !focusable && element.Focused() { - element.Propagator.HandleUnfocus() - } -} - func (element *Container) childFocusRequestCallback ( child elements.Focusable, ) ( diff --git a/elements/core/selectable.go b/elements/core/selectable.go index 11d100f..824dcae 100644 --- a/elements/core/selectable.go +++ b/elements/core/selectable.go @@ -1,5 +1,6 @@ package core +// import "runtime/debug" import "git.tebibyte.media/sashakoshka/tomo/input" // FocusableCore is a struct that can be embedded into objects to make them @@ -71,6 +72,7 @@ func (core *FocusableCore) HandleFocus ( // HandleUnfocus causes this element to mark itself as unfocused. func (core *FocusableCore) HandleUnfocus () { core.focused = false + // debug.PrintStack() if core.drawFocusChange != nil { core.drawFocusChange() } } From 5149c27cf32c44f2e6b09c5d2012d6adc49ebefd Mon Sep 17 00:00:00 2001 From: Sasha Koshka Date: Mon, 13 Mar 2023 17:10:27 -0400 Subject: [PATCH 16/17] Added untested label collapse --- elements/basic/label.go | 38 ++++++++++++++++++++++++++---- examples/documentContainer/main.go | 12 ++++++++++ 2 files changed, 46 insertions(+), 4 deletions(-) diff --git a/elements/basic/label.go b/elements/basic/label.go index 2674715..c14674d 100644 --- a/elements/basic/label.go +++ b/elements/basic/label.go @@ -1,5 +1,6 @@ package basicElements +import "golang.org/x/image/math/fixed" import "git.tebibyte.media/sashakoshka/tomo/theme" import "git.tebibyte.media/sashakoshka/tomo/config" import "git.tebibyte.media/sashakoshka/tomo/textdraw" @@ -13,6 +14,9 @@ type Label struct { wrap bool text string drawer textdraw.Drawer + + forcedColumns int + forcedRows int config config.Wrapped theme theme.Wrapped @@ -56,6 +60,17 @@ func (element *Label) handleResize () { return } +// 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. +// If the label's content is greater than these dimensions, it will be truncated +// to fit. +func (element *Label) EmCollapse (columns int, rows int) { + element.forcedColumns = columns + element.forcedRows = rows + element.updateMinimumSize() +} + // FlexibleHeightFor returns the reccomended height for this element based on // the given width in order to allow the text to wrap properly. func (element *Label) FlexibleHeightFor (width int) (height int) { @@ -134,20 +149,35 @@ func (element *Label) SetConfig (new config.Config) { } func (element *Label) updateMinimumSize () { + var width, height int + if element.wrap { em := element.drawer.Em().Round() if em < 1 { em = element.theme.Padding(theme.PatternBackground)[0] } - element.core.SetMinimumSize ( - em, element.drawer.LineHeight().Round()) + width, height = em, element.drawer.LineHeight().Round() if element.onFlexibleHeightChange != nil { element.onFlexibleHeightChange() } } else { bounds := element.drawer.LayoutBounds() - element.core.SetMinimumSize(bounds.Dx(), bounds.Dy()) + width, height = bounds.Dx(), bounds.Dy() } + + if element.forcedColumns > 0 { + width = int ( + element.drawer.Em(). + Mul(fixed.I(element.forcedColumns))) + } + + if element.forcedRows > 0 { + height = int ( + element.drawer.LineHeight(). + Mul(fixed.I(element.forcedRows))) + } + + element.core.SetMinimumSize(width, height) } func (element *Label) draw () { @@ -160,7 +190,7 @@ func (element *Label) draw () { textBounds := element.drawer.LayoutBounds() - foreground := element.theme.Color ( + foreground := element.theme.Color ( theme.ColorForeground, theme.State { }) element.drawer.Draw(element.core, foreground, bounds.Min.Sub(textBounds.Min)) diff --git a/examples/documentContainer/main.go b/examples/documentContainer/main.go index b14e9f8..7326302 100644 --- a/examples/documentContainer/main.go +++ b/examples/documentContainer/main.go @@ -4,6 +4,7 @@ import "os" import "image" import _ "image/png" import "git.tebibyte.media/sashakoshka/tomo" +import "git.tebibyte.media/sashakoshka/tomo/layouts/basic" import "git.tebibyte.media/sashakoshka/tomo/elements/basic" import _ "git.tebibyte.media/sashakoshka/tomo/backends/x" @@ -44,6 +45,17 @@ func run () { "lay out a settings menu with descriptive label text between " + "control groups like in iOS, or list comment or chat histories.", true)) document.Adopt(basicElements.NewImage(logo)) + document.Adopt (basicElements.NewLabel ( + "Oh, you're a switch? Then name all of these switches:", true)) + for i := 0; i < 3; i ++ { + switchContainer := basicElements.NewContainer (basicLayouts.Horizontal { + Gap: true, + }) + for i := 0; i < 10; i ++ { + switchContainer.Adopt(basicElements.NewSwitch("", false), true) + } + document.Adopt(switchContainer) + } scrollContainer.Adopt(document) window.Adopt(scrollContainer) From 99e029ae0974deabc177ee6276edebd50ccbb5c1 Mon Sep 17 00:00:00 2001 From: Sasha Koshka Date: Mon, 13 Mar 2023 22:25:57 -0400 Subject: [PATCH 17/17] TextBox no longer aggressively requests focus --- elements/basic/textbox.go | 1 - 1 file changed, 1 deletion(-) diff --git a/elements/basic/textbox.go b/elements/basic/textbox.go index 98296dc..a76358f 100644 --- a/elements/basic/textbox.go +++ b/elements/basic/textbox.go @@ -81,7 +81,6 @@ func (element *TextBox) HandleMouseDown (x, y int, button input.Button) { func (element *TextBox) HandleMouseMove (x, y int) { if !element.Enabled() { return } - if !element.Focused() { element.Focus() } if element.dragging { runeIndex := element.atPosition(image.Pt(x, y))