From 5d16f5d50c6d856663339eca3559357c96d2f56f Mon Sep 17 00:00:00 2001 From: Noah Petherbridge Date: Sun, 25 Jul 2021 20:53:09 -0700 Subject: [PATCH] New Widget: TabFrame * Added the TabFrame widget with an example program and screenshot * Button: FixedColor=true to set a consistent background color and not worry about it with mouseover/down events. --- README.md | 9 +- button.go | 29 +++- eg/README.md | 2 + eg/tabframe/README.md | 11 ++ eg/tabframe/main.go | 267 +++++++++++++++++++++++++++++++++++ eg/tabframe/screenshot.png | Bin 0 -> 43557 bytes frame_place.go | 2 + go.mod | 8 +- go.sum | 165 ++-------------------- tabframe.go | 282 +++++++++++++++++++++++++++++++++++++ theme/theme.go | 39 +++-- window.go | 15 +- 12 files changed, 648 insertions(+), 181 deletions(-) create mode 100644 eg/tabframe/README.md create mode 100644 eg/tabframe/main.go create mode 100644 eg/tabframe/screenshot.png create mode 100644 tabframe.go diff --git a/README.md b/README.md index 8454e9d..62da611 100644 --- a/README.md +++ b/README.md @@ -113,6 +113,7 @@ most complex. * Place() lets you place child widgets relative to the parent. You can place it at an exact Point, or against the Top, Left, Bottom or Right sides, or aligned to the Center (horizontal) or Middle (vertical) of the parent. + [Example](eg/frame-place) * [x] **Label**: Textual labels for your UI. * Supports TrueType fonts, color, stroke, drop shadow, font size, etc. * Variable binding support: TextVariable or IntVariable can point to a @@ -136,18 +137,20 @@ most complex. * Works the same as CheckButton and RadioButton but draws a separate label next to a small check button. Clicking the label will toggle the state of the checkbox. +* [x] **TabFrame:** a collection of Frames navigated between using a row + of tab buttons along their top edge. [Example](eg/tabframe). * [x] **Pager**: a series of numbered buttons to use with a paginated UI. Includes "Forward" and "Next" buttons and buttons for each page number. * [x] **Window**: a Frame with a title bar Frame on top. * Can be managed by Supervisor to give Window Manager controls to it (drag it by its title bar, Close button, window focus, multiple overlapping - windows, and so on). -* [x] **Tooltip**: a mouse hover label attached to a widget. + windows, and so on). [Example](eg/windows) +* [x] **Tooltip**: a mouse hover label attached to a widget. [Example](eg/tooltip) * [x] **MenuButton**: a button that opens a modal pop-up menu on click. * [x] **MenuBar**: a specialized Frame that groups a bunch of MenuButtons and provides a simple API to add menus and items to it. * [x] **Menu**: a frame full of clickable links and separators. Usually used as - a modal pop-up by the MenuButton and MenuBar. + a modal pop-up by the MenuButton and MenuBar. [Example](eg/menus) * [x] **SelectBox**: a kind of MenuButton that lets the user choose a value from a list of possible values. diff --git a/button.go b/button.go index 68d427a..f5e006c 100644 --- a/button.go +++ b/button.go @@ -11,9 +11,14 @@ import ( // Button is a clickable button. type Button struct { BaseWidget + Name string child Widget style *style.Button + // Set this true to hard-set a color for this button; + // it will not adjust on mouse-over or press. + FixedColor bool + // Private options. hovering bool clicked bool @@ -22,28 +27,33 @@ type Button struct { // NewButton creates a new Button. func NewButton(name string, child Widget) *Button { w := &Button{ + Name: name, child: child, style: &style.DefaultButton, } w.IDFunc(func() string { - return fmt.Sprintf("Button<%s>", name) + return fmt.Sprintf("Button<%s>", w.Name) }) w.SetStyle(Theme.Button) w.Handle(MouseOver, func(e EventData) error { w.hovering = true - w.SetBackground(w.style.HoverBackground) - if label, ok := w.child.(*Label); ok { - label.Font.Color = w.style.HoverForeground + if !w.FixedColor { + w.SetBackground(w.style.HoverBackground) + if label, ok := w.child.(*Label); ok { + label.Font.Color = w.style.HoverForeground + } } return nil }) w.Handle(MouseOut, func(e EventData) error { w.hovering = false - w.SetBackground(w.style.Background) - if label, ok := w.child.(*Label); ok { - label.Font.Color = w.style.Foreground + if !w.FixedColor { + w.SetBackground(w.style.Background) + if label, ok := w.child.(*Label); ok { + label.Font.Color = w.style.Foreground + } } return nil }) @@ -83,6 +93,11 @@ func (w *Button) SetStyle(v *style.Button) { } } +// GetStyle gets the button style. +func (w *Button) GetStyle() *style.Button { + return w.style +} + // Children returns the button's child widget. func (w *Button) Children() []Widget { return []Widget{w.child} diff --git a/eg/README.md b/eg/README.md index c6887b5..5d80753 100644 --- a/eg/README.md +++ b/eg/README.md @@ -14,3 +14,5 @@ screenshot and description: * [Menus](menus/): demonstrates various Menu Buttons and a Menu Bar. * [Themes](themes/): a UI demo that shows off the Default, Flat, and Dark UI themes as part of experimental theming support. +* [TabFrame](tabframe/): demo for the TabFrame widget showing multiple Windows + with tabbed interfaces. \ No newline at end of file diff --git a/eg/tabframe/README.md b/eg/tabframe/README.md new file mode 100644 index 0000000..57a6126 --- /dev/null +++ b/eg/tabframe/README.md @@ -0,0 +1,11 @@ +# TabFrame Demo + +![Screenshot](screenshot.png) + +This demo shows off the TabFrame widget, in multiple copies of a +pop-up Window widget. + +## Running It + +From your terminal, just type `go run main.go` from this +example's directory. diff --git a/eg/tabframe/main.go b/eg/tabframe/main.go new file mode 100644 index 0000000..ebbefa4 --- /dev/null +++ b/eg/tabframe/main.go @@ -0,0 +1,267 @@ +package main + +// See the MakeTabFrame() function just below for the meat of this example. + +import ( + "fmt" + "os" + + "git.kirsle.net/go/render" + "git.kirsle.net/go/render/event" + "git.kirsle.net/go/render/sdl" + "git.kirsle.net/go/ui" + "git.kirsle.net/go/ui/theme" +) + +// Program globals. +var ( + // Size of the MainWindow. + Width = 1024 + Height = 768 + + // Cascade offset for creating multiple windows. + Cascade = render.NewPoint(10, 32) + CascadeStep = render.NewPoint(24, 24) + CascadeLoops = 1 + + // Colors for each window created. + WindowColors = []render.Color{ + render.Blue, + render.Red, + render.DarkYellow, + render.DarkGreen, + render.DarkCyan, + render.DarkBlue, + render.DarkRed, + } + WindowID int + OpenWindows int + + TabFont = render.Text{ + Size: 10, + Color: render.Black, + Padding: 4, + } +) + +func init() { + sdl.DefaultFontFilename = "../DejaVuSans.ttf" +} + +// MakeTabFrame is the example use of the TabFrame widget. +// The rest of this file is basically a copy of the eg/windows +// demo, except each window embeds the TabFrame. +func MakeTabFrame(mw *ui.MainWindow) *ui.TabFrame { + notebook := ui.NewTabFrame("Example") + + // AddTab gives you the Frame to populate for that tab. + + // First Tab contents. + tab1 := notebook.AddTab("Tab 1", ui.NewLabel(ui.Label{ + Text: "First Tab", + Font: TabFont, + })) + { + label := ui.NewLabel(ui.Label{ + Text: "Hello world", + Font: render.Text{ + Size: 24, + Color: render.SkyBlue, + }, + }) + tab1.Pack(label, ui.Pack{ + Side: ui.N, + }) + + label2 := ui.NewLabel(ui.Label{ + Text: "This is the text content of the first\n" + + "of the three tab frames.", + Font: render.Text{ + Size: 10, + Color: render.SkyBlue.Darken(40), + }, + }) + tab1.Pack(label2, ui.Pack{ + Side: ui.N, + PadY: 8, + }) + } + + // Second Tab. + tab2 := notebook.AddTab("Tab 2", ui.NewLabel(ui.Label{ + Text: "Second", + Font: TabFont, + })) + { + label := ui.NewLabel(ui.Label{ + Text: "Goodbye Mars", + Font: render.Text{ + Size: 24, + Color: render.Orange, + }, + }) + tab2.Pack(label, ui.Pack{ + Side: ui.N, + }) + + label2 := ui.NewLabel(ui.Label{ + Text: "This is the text content of the second\n" + + "of the three tab frames.\n\nIt has longer text\nin it!", + Font: render.Text{ + Size: 10, + Color: render.Orange.Darken(20), + }, + }) + tab2.Pack(label2, ui.Pack{ + Side: ui.N, + PadY: 8, + }) + } + + // Third Tab. + tab3 := notebook.AddTab("Tab 3", ui.NewLabel(ui.Label{ + Text: "Third", + Font: TabFont, + })) + { + label := ui.NewLabel(ui.Label{ + Text: "The Third Tab", + Font: render.Text{ + Size: 24, + Color: render.Pink, + }, + }) + tab3.Pack(label, ui.Pack{ + Side: ui.N, + }) + + label2 := ui.NewLabel(ui.Label{ + Text: "This is the text content of the third\n" + + "of the tab frames.", + Font: render.Text{ + Size: 10, + Color: render.Pink.Darken(40), + }, + }) + tab3.Pack(label2, ui.Pack{ + Side: ui.N, + PadY: 8, + }) + } + + notebook.Supervise(mw.Supervisor()) + + // notebook.SetBackground(render.DarkGrey) + return notebook +} + +func main() { + mw, err := ui.NewMainWindow("TabFrame Example", Width, Height) + if err != nil { + panic(err) + } + + // Dark theme. + // ui.Theme = theme.DefaultDark + + // Menu bar. + menu := ui.NewMenuBar("Main Menu") + file := menu.AddMenu("UI Theme") + file.AddItem("Default", func() { + ui.Theme = theme.Default + addWindow(mw) + }) + file.AddItem("DefaultFlat", func() { + ui.Theme = theme.DefaultFlat + addWindow(mw) + }) + file.AddItem("DefaultDark", func() { + ui.Theme = theme.DefaultDark + addWindow(mw) + }) + file.AddSeparator() + file.AddItem("Close all windows", func() { + OpenWindows -= mw.Supervisor().CloseAllWindows() + }) + + menu.Supervise(mw.Supervisor()) + menu.Compute(mw.Engine) + mw.Pack(menu, menu.PackTop()) + + // Add some windows to play with. + addWindow(mw) + addWindow(mw) + + mw.SetBackground(render.White) + + mw.OnLoop(func(e *event.State) { + if e.Escape { + os.Exit(0) + } + }) + + mw.MainLoop() +} + +// Add a new child window. +func addWindow(mw *ui.MainWindow) { + var ( + color = WindowColors[WindowID%len(WindowColors)] + title = fmt.Sprintf("Window %d (%s)", WindowID+1, ui.Theme.Name) + ) + WindowID++ + + win1 := ui.NewWindow(title) + win1.SetButtons(ui.CloseButton) + win1.ActiveTitleBackground = color + win1.InactiveTitleBackground = color.Darken(60) + win1.InactiveTitleForeground = render.Grey + win1.Configure(ui.Config{ + Width: 320, + Height: 240, + }) + win1.Compute(mw.Engine) + win1.Supervise(mw.Supervisor()) + + // Re-open a window when the last one is closed. + OpenWindows++ + win1.Handle(ui.CloseWindow, func(ed ui.EventData) error { + OpenWindows-- + if OpenWindows <= 0 { + addWindow(mw) + } + return nil + }) + + // Default placement via cascade. + win1.MoveTo(Cascade) + Cascade.Add(CascadeStep) + if Cascade.Y > Height-240-64 { + CascadeLoops++ + Cascade.Y = 32 + Cascade.X = 24 * CascadeLoops + } + + // Add the TabFrame. + tabframe := MakeTabFrame(mw) + win1.Pack(tabframe, ui.Pack{ + Side: ui.W, + Expand: true, + }) + + // Add a window duplicator button. + btn2 := ui.NewButton(title+":Button2", ui.NewLabel(ui.Label{ + Text: "New Window", + })) + btn2.Handle(ui.Click, func(ed ui.EventData) error { + addWindow(mw) + return nil + }) + btn2.Compute(mw.Engine) + mw.Add(btn2) + win1.Compute(mw.Engine) + win1.Place(btn2, ui.Place{ + Bottom: 12, + Right: 12, + }) +} diff --git a/eg/tabframe/screenshot.png b/eg/tabframe/screenshot.png new file mode 100644 index 0000000000000000000000000000000000000000..64420c157c3844bb5899f010e3dc19aec5d79224 GIT binary patch literal 43557 zcmb@ubySt#+BFOYAV`QHT}lZ8(hW*VcSwhHcZbs5T_VjUq#Kl!me_1ky1N^`wSS)T zyyyA8GsgSR=NJm^xc6Gux~_T6IoG;(h`g*gIw~P50s;cM#0L>Y1cZkS2ndKRC`jND z{LK_g@Z*t#kc2V{_~VK4DHs8P96>_ly|Qci&YYX8^6J#Z;r@;k^>rV1C4zp4zZZQa zlM{VmxiVrAy;W(clNkf_%%rH*tF%TnoBQWBRlUWOy4GJxR?;RAmx>|dGEvAv&I zX7;0^DTJwerGFvKW^MNw*$+8nt_uX&8Sgb@9fv<4FY+lSqVUHOLZR^AFqS3d{U%Ry zD(58L)#>8&2L2#;W-&|?UEZY1*@^t$55!eM9{%$dETQ1U52G*t_5KM;vuA%_;%E_D z{rAljjqFZp$mD-tQP1aJs4{@U9P{E6xmYRU*WcFbdNK?|^pHYFu9@+2T?r}M4_IyY zxBIWlYmvv@OrulV1a8?>5)wHc)A(bNtbPvi>9}Ht^xoVxm0_$_(!exOD9WDZNQ>=- zhP*33$Q*!*7vx3B_J_ZHZu26cqM(s_q4ic$BOHkY-|*6M`R)oG^>HY5X{vR@abPw+ zI*)_~xau0Xs<4QN|FGA7^Ci+0O=Mls{3Y5xxzP`Va;xjJy~Qv&K{F*El2yeJB@~Lz zRjc^kU&Dquo}V7AZ{kXNlM;2&$3IH1zu@QJDQ>NJ(`e@SE(23K_-SDame8Xus1Ji^ zcA-yb^F0jn=ybZ~6UpGCuVrO>->&}Do)Xns-F6?>jZM|YI*T_cFX0FU?~!wH;rkXB z7c)k#96kG{YNsacPorPaIEx?uXH%-QKR~BQ1aTIZy8}XO>^N78DT_krmT`~!!s7gQ>(eCZ zRF;UzdM;Gdp~@pcdVF^Gr?12!x>xNh9j={QZ;jCqG)faK%G*BwR73x{_&u)G0#B2} zrdaSjnP(R_*D0NKGHX!Yy(v*q{@@{Ei;E6kmO!r5PS zuCP<>z_{I{Q8+5!7JE~79>v<%s(y53Xb^bOqS$+a^*k#f?UuA-AKH;FOM?;g>MPl0 zwH(Sw!q(Q-WP=wkQ_BS(S-JX8XdLxej=0?L9fU^4c=^idW;?*g_Dovf*vB4OGNG@$ z)h6@W5QCKK$MLX0#}uoH2FZ5nwzb~vG!*{I1^dSObCdJz5p%awU8&2Xtf;W-!~tX=XoCNHHG7|hdz5HhUB<6Pr^O0Ijdzna7BTjlt1`2w?5d90Qsho z<8vpP?d=ZHRJA_;z5AhCXc+2c@0?4{h-qpkloTEut8B%h`osLR;K)b{7M4&Vn>(`c z;(iAix;({@{#Ap1&Ut&fb2fySLFqheOtbf5VQzwy0qr5fg}(}#Yb8#Mxa~Kv5T<&f;_UR=<_$?E+oF`X zxe<2en}W7Yh8MSMEWXd|Dl03?D{4tgOTS6nGA|>H?FJDQHZr1GH8R?nYDt5P23Fea zlV+{E>8I)>13JpW#n%vMRyWEme49bm} zts@g>Z95M)dyD@-fRn=C6caVzi$z&Wq`$w&aP7sPLoJt%(~_&DFlV1OcNj_C_3Yi+ z%zn)_S1T=j?|n=GgBOB@Te7ZzOtqwfN0g6ck1_PTZ?C7|c&E*YieW zv4{6Dg8V8+727*H_-D>?_l?)j?%nUoD$;e8S57u(e{|FF3nmU4=M-0tZgWFbO@tw1 z*aWx%ibX4Un0y;k1&T$5g|s$jd}(1-m7@cR5k$!jWB1?9I$-e$iM1ZhtiBHeI%0y?g8|vhK6uCjX3jsiL8a>-osvk$-m^6XUR%o-|u_5);j1 zZ}B)cILd`g9SzpiYcaOY)wXnW)7A0@!!qy5w|AFVYSmO&d0*oAGW88?g!a|buiXTF z6vb;Es_x$3)W<0B!$7*avq-U<0g$# zYQC3Wjw)HoIdg($Z2}Yp-s0G05gp!ejszY%* zq59LfdIL90_;$w@-k~Brj^$gA@(Jg!-GrbXsTod{Xm${(83=2{Uid!~a(v<-`uIKd z^Cux8$@FyioQvj;JS}Qzi?77(>1cvRN3p4;)BU05RJ_+BLE)=RVO{Mp!a)}!5u6pE zAeq&YLqf~4RGpUU_xIBkAkdA|^&SVF46bxp<9S!s`&(!F0(;p?qh%!cf6H$y%)sQn z-f3j!))yvAC>0nKm94EjhE+1af#;=dR{2%2QqPR~$FA`2$MnIsudr{#e?Jj@FD#&O3U4b_IW~+E-ZgIG^S33(Wu6U0cQ_ zv)?@xd`ZWX`lZUm?LMP!hE1giTJhlIvVcCtX|QIX>6T{0k%x|sZt}aeRn=~JCR+-C z=d+s@01%R)VZTjlGWTt!YAk3v#KKp$B;qcOkMo@)@D^qmG?SUP2@MPkU(>z$)P>yd zyH87waz1QjZDBl#Ft1RMkItOu z^CX+lfZTHjKL~e!<>RqO^(L|31>KUuzkZbe;J`+`&6qu9-UkNFhEpk_e@IE>yYiFU zQaaV7G|n)|DT|^fh4YdQAwCH&!txRYZ!fujkjRfdKpo~(LHkR=7|%96V|Yc{)dlo!dcHhG;J=GlHo633$~Vt{nyJy>hc4x(liBJT|TPy z{EOLPksiPJ^YBBS=wD7ZGAQwX;e0iWavEh#dpxq~Tq98Ze>1_?P8R6-vKf8H!-B)V zvxWI*5}9;|hBA0RHafmWI9?xKrxCn-0@HGcfK@m-IZ3Co6-~9?pA-nzFoP(=huoR) z4Vt@ZU|OT$yf?dfc^xEpLm|h1ijyNAb!O>t*oDW-^5|c$q$=w#2twSMZHRL_USqbJ zX1hBG64a}k;lBnI=z4S5lfrKCg7c5-D>Bc6&nELuFzp_T@|JF^y1DN7*Gd=tx_9j% z&uQwf_nK1p+*mOAPq1Eub(OdNJ90{OA2e!h=7-=@@wq!D0~7t3%5H&R+cbBx=hF7- z<;yP#^lC@#XiNvaG+77p_jfP{OEEE|`8q<#JRb#87h!ds(092QpA+dZj&t=w&OYIPyhoERer#u3!< zSu)SOj$BAsn7?h?k!Qe|72)yY$I2=ycBfFaho?~Nx|zX3g&a>(2-H07#;R)S=6_wM zv#Tp6D{J^y%q#wxqvPYvsVb8oBqS^%A}M8M9EfVEP`12d+P^21Q2_POFdj_pI$G`L zcHY4U9Rt2NWuC}*Vr>__pBk^=SV~&pOL5-`|nB$W9+}I_ZQT>hj24AVF z#@cXJ)S$89;Zj$8eEjbw7n>JMIs*rb9lU&8d5XQP!+afROwB)TE{_qDx$IuHU34)u zRx|z036XULN}z2X0^U_w7WuDSrr=v+XhzvUra{Pe*!g^yAP7syF_b~G_L1j+Fed+_4|%Hw=!9&A@b?5W9^RhMyEvgF zHFMAaP_QeSZg41=x9W)?f_;vGCr>-sKay@k+0M>Ro8$}pQnkwG0X7-2)m#~}vKc(9 zm%QBE4NyZ+Uxh_Qh(Ao(u%A5}(LZFjZO}>=@FENlLVTAc;59uir=o%j_+!_qeYQO9 zTf&IWo}OzbODS02s(nmS()wjn>ERRjiUt389P2Ng!jjjqRFAE#JgTUyEMjHFfIuA= zvbALn_&Nd#DymSCvO(1pC_7U&LhrldtSZM%#kQ-mq9Xmy;Ai!7)@S?kJ|hzShs!;7 zT*R4ePCO(M5)wLAV2}vOoHnHUE$5^uDJe2n3PsBPfq~d0B)byUjDV7M{`mYbh2Mk2 zWGG#KwJ*WDXVbnxzDW6vMs;wWR8nk42D-0MHaP_aqVMJL`tgLOjl#!|5+6PU&wKsN zBfHm)g}GhUb*yHily6?3H97CLFWMf&W!*fgni>E}j`)KerX~FGBSDkP{u!^n*~ZMO z{i1yX;KO*&^LuQ3t~I5ppNoo$HlK`2$(*SvE5G%=am66!^6g&*Q+9trY|BNQJZOwT zCNNMp?;SR?OU=PCvCaipC4jA?ZXz3bEv@ho(-U5?r2c@J-Lo4IpS{Ly#=FyUA4GXI zHF0NW4nJhfwT+P+__{J$C>GgdNqTvJD6Oomxerw>b_k|CA@|=f#Y?CLaf~Dr9J)AM zo~pGXGOr=5(U5U;tX>~$;gS!EwQ7 zCPqeX)ATr~C+_@o34K*<6cr%Yh0xQ2JXOf%NKTMRc-!r+jfjZITbn<8a`Wg~jCisI zan;pqf84hj+S}V{85p`i>{jCBzCq1HLP8c(Ey~_Qpq18hJb+PCQBi^XlhcyS18^l{ zYx@=i4MF|Qi}KSa^6ni42+`i5DBU%eJ?^GkPQ`=KTTMM_GC0REzAzi?!3ADlX;Y zG}P4gCmSOiUE|4Pn<52c6}F2)I^LJ7)YnGTzF6OKw3J7&2?-^wtr_cP?a?IjVlOUS zWwQj}gN%uZ0pod!^bm{||0&Y&b2E8)tgsghFPWI4#>bV1GWq+1$h<#@ias7IkTU@v z0@nKI=!lk)v3t{z#coNYJCYkBO5Oy}ez? z{d8+p!uZB5`6s8=baYw~7iHd2;F7^OTD?(*|?%@|Zz0bL1gG z#w7hT*XZ=<@ne0K6yH7FBA2E)tH>heg0W3@F+-N;?@&f!80pNOprXDM6wLbe?IrjS zQAI^;8o5kLN=pCSTuO5bi{9Se_u2AnZn0tE;VYn>8k}xH2QvB7e)=IRsj7-eN}@8g zoM2nmrGe^J($bO^5_(`}X4ZUnwLQ7(3?Csd)qeH$Ed>%;!^4m#9z{A1g=WLHC1hpK zE<+ThKoTSQ{frxDQ??_-j}Tj0TKY9OC|as&_!d7nJe-n&A;PFXQ9((m<79J;fZZIY zKapX%uI-k==Xz$IuZOc)-rwKf_IkI@;o?AlILy2Q&7uQM){xBWBnEc!Hm-NY%t1dK zAY@lp7qiEi<=sV(;87Qz?yq~bj1|S=aZqhb%gY~YEeXIBKuZ5^^YM{$T2(9>r%&j6 zVMxcsbk3twq>N2K5He+XrsWVI^j=;*xg(@Upv58Pw)dZ$RA*sfLBYVtS1Z>6on2mC z{X?7g?FU&|cvtRlj~70&E>Z4eV>vXKUIhnO?^DwDPcsn(k)1(_Emegy_C;V1&VD3U7;@wAEbuY z_}qH~NP`zG$TLv1<;7|s!C6g)J^?NRAQNyRfCKdgp0}s4S-Tz*%RkL2%!b_u(-!4U zP9KtL!gP+fXla8%yvkb6G>9>f0qKoTOvJ{;4N!#`ZBLXYOH-HAxN_aEX_cyaWR8c1 zg%NPuQ-X%kz3a?syYLP~jFyhhU?7=!=T8e!)10iiIo^FD(@o7A}O)WWFm>wm(>S4-gLp6BC&+L$XD&$dZ{F0^M?W;|rXRiIR^U z>sdZ~Mb+WN+_CZTI06?74?)TCyPrg-AS{K#(W};KrvCho$K~z5&wTFm>uH!M4MTQz z_R9MDb6ni6y}2gPg}^F>hK2@5MfF&60H7r+QKd%>_|_Yx3K7jy1c(X}cyY4AaMtJk zHdC(etH2_c=fzvW+f6wIx&&eY&ty;+Ik0UwpUn}AFt|Kg1@^0ukkDk638s0?^!5|N zg-nmV!xGcVPrcX^W!n9qH~U`E90$rRBB2wmNRR@j0RZC9O-Drqg`Y96#3vh_=zsqF ziGzb4!5BH$`RH;#sY)0!Al;DWrNp#ji!H*1!;IvcXK zn^mUQ3=FF~Q&lo$Q5}oqG&Dh7VR#NV7j^ zawt;Xvaqn=_hS55>HqaBx%Wv98MFB~ZR`D=$8dw=xuh4ULq5(mPP{lkC%9EX0q5oA zeKIk5{`|SHj0`#j1%-45PlC_=_5Aq6gu5!2h3jq{SOZCr6TMJ1-NEhhwjsdB-F9nW zly;l%@G`1F!viLe&t<i=zC15e2> zdT;S3>+N2Fo`bNZivoIpSWXP!z3#2Q#k1wH2nj=bKfR=a$* zK(5unyh~N3K72q)#mXxf7 zF}3RbX?F8Gn=(n~c0kIIrC~+v#5GzQNNK)3n~6_I04y1w*ZB>v^Uem~BAOt8ptUM0 zDr#84M%@oT&wAKre}0I31ua_)BhUn#`*WAUq93NfyFA63X%}_%WZeqg3#!Bj0Esp3 zp2L~^pO3c}m~>l-Bg9_OR{`p0mL!>X2a<-8hUR#|4|B=Bfy@1b*5hpFkPlgxF5wE7 zUX_jLpT(aD@G4fO8{P-??0GUGK5O640Ky#boJRJo`-v%_i)Hv^-WS>H=Yg2QB0YcA z)+ZB^vu(IadB9fm4oLVsJ<#e9OZQl;XSqn-Hz*K5g)~H>Zil`#{Y}1b6T}WWmSIn% zTk%u$gsZ@W3S~N6!1;7^lTp?Me_~={o>x0HXaVjdrwf0+NHanW)1q*k^NvL%LTuUW z`Lk#5KYhxj>5h%X1{|Ny3v@zIkN|IvJ3+|4bQ$R1-CQ;UTo6ctwS8Bd|1ST|P%>{c z?kyD{b5|#3<*kHtNA4|uAVOpHiu1ODAv)zYil=6b`JjUUI7$tN+G-OTghoZ3dp3OP zVCh4L$>b?k0P6C```QYqZxnQND?3XHMp5)C?;AK{k-JXJw+T(8D$sgdEev;17}=M* zBeu7&9wmw8>45YlApu<4F>JTdBkvCxLj*Lr+z0b7^R|-J5V+o{It$N{(k@<)O-b22 zKdB#zhW`x9#L_kbS%XNnygf4*U@Bdte8rA_X`iP|$B#CLLLnkvUnm*Y9Q}_N38oJy zVVQFJ-;kPuLac}B04 zL1Q>c;h53^oM)*kjF+GEuPWCVE8|?^&5)&*PF|?70uXw#JCg)b?qW_3uwS@fbrq0B%_W%YOh^9FQ#-h;22hlf36RZiQg0Qo^o4fkdnHXIwB$qM+VcAZ}^XskHQ z8%FW3IW&!!t^kDD`}O^=kzg9SFKAO!e2k>$qzW=R#13`$#?KBDE#p$ zWCrd~^pJXlM@M5h?@Yo08lTxfBq(Bvl#LMGrsebn!ZxhDprj-m*f8jYSu<_STd zEDmeZtyfzRSrWB!7^~BkGH6$0Ma#8yPo7gR*f)TN(j$6q#FC<3Z%e_{c2fy+ZqXV3 zCK{&R=xA2Z{mQ?-(y=eLM@2e=M7`q6=%}LiDMXjq^+1oqdiGZ+EKz#9 z1lalORn&J7gUYHZcvpy{mRU+M$ux5c2bus83v{|Um-)d#&iSX~YCef?KDJMkY7(%S zVgb`CD7@2_A0=moAeUUAs`k^c0F z5^(O5Ehzo({y%S$`nv$UH+MS%yXO8WE@U&UaU^TG{fh z0$OI@-A~O2j2+%R0jkCD>SVJ-r)Aor3CN!w!MloK!`7Q4DX?Su0J9O7)p$Vf;$V?B zK@x+6r!x?ps1O3NIJcXQ(gqy#F3LQ5Ys0vc=3w`5(FIz*<=-KDb(wr2!4=;q}dK;8{x31&6zx26HR)Y+6z z2nF$!tnd*_0v8sL+kj0V_#!?;(qki>0%8H=e{^#4FQ9T2%%@^zs0YfN(F@Kj4C zd9>HGKMF<#cUe0y1$WFifdU34*b#`H1DqrsFW@hwW(x-a`v(07a5Q@R`(LrMC)C$- z4c7@{;=e@z(iiAZaK+5f-@q(<7}~(z+TAPKiVNVnp@)kr8 zc(es@5tEQm;dSi{X!mXCjB%BIg*xBxCfw}VAp*_(Ikoh=a#&nKLYGOFS767Y9$4a= z1!O^THZ~k!5P`+UM4*;Q?F3i>Xeu$EYc#vX1QrfXCyC4Kq8kIZ$Jv|xwtE2}w?Nq7 z%@>%rZfJ$YyySJpqfxck){p zcrFM->DYWF4#mAkF$9XrTB}(a_+jBOs6oD7yDqRTe;*#iBxD zLGN^@?Fl%XmrF_kcVG&TJ5aemhkz^|>I`{~$Y(_exH(YnUBHP+=CC3J?#b=-Ip@cP z1%nI1Vk(mav`Mb@$fb;$z8Q@L52nzt|0~n3( z)mCHqrapsN;SazlspT@It*y&o>cG3wa)PQnC1CMCIdN>e;sbHjthHPOvi@DRydFo| z<-X57+|CB#A3PBbbQBySFUc*ydcj!)kgBekNuUfqNJ%Z9!gPCpA2B2{QY>c#;dJaP zoCTMaorOCctw;<%Mf5Q;G6Fuq;~&Y)hV{U(=14VaMJib3uhZ)rIb0kMRtvNd? zB7%yZJ`C&>Ab=|i0pO#NP5+vk>z|%Zi~uw%bBZ*e^T6oi!#z36j9jxAKubtWgxj|7 zz)6E3c$2lSwix}USmc?wnJk?sI`@n>m+sb6Sdt zh#UZzy|Bdl<;9Gr2!YNzj>*9P%AA1>mg@A~Y3|przO%DhK(N4Fo^&}+Pd)%nCUAo!ArZK5-QffsKnGXM?B?d@yYF>1e3vYf zr-fSzE5BmkmIB!S0XBH=uU`kedlT@oizxr6=<4kq-Tv7OGz|k;z~Xsf3-lb|D?mgb z2X(x+vtx!Ep1?XcJI#cD5YkipAK* z*C}5IFN2F+99&#GU;zAo6mXTtx%CT1tq*XsAu~~_K-LcUW8R>jd{d@_V=yoWJ5AX* zc=rIus=vRx=0A^i%-e%w-Dn3%={NwtEdY(X zyY<%J)J5=mY8YYu924!H5|Dl0%CP0Xdp<(JFaG;ZkNqnFN659%gORzVL>2vixG9{s z=Nc{i_?)yO6a39^)_uDazS*){mbVHE0GAs%%>8HUwOYXi;(85f>YAHd0!IP_W2=Lm zsS-HtxMO-8dSWD7>g790A{d9p06r`)}L7)Drzv zYaz3(;r;miep998)koLqckut@aKGLq>?oIgYM|AvEjX-kE7clW&~@Xk;xmFo5A;=gF*i)D4<375os81ce7f=@Zx5CL zekg^`jMR<>hpNc;ikDxqdRFb$ANLAdbSKmGR&7pcpfX38ta|ZT1QK@#_`TjN>CcSr9p}{LO-%(X5jM`B2*XrPNirX%h2&V$!e7 zEi7eJ&M<@H2F7p6wl#j~8vNxiI;%=q!qrgS;|l9osHz9{y~Q7=UFNcN)4xZgt+wE{ z8hZ{Y%K2SJ)j~H8Nd@Ei)}Gy6=*Tj;q5at`N?k3nb5v8U{~^CUfqro(&by;?ShGlF z$@`6XM(?RtNd)>vkBZViO*}eWN)hmsCx*GxfWr7ZFk-fHp5@1VMYnT>5%Y7t%J-#JNIe66Nl^^Z6A%l(4HpW=7*Kp=k+;PR?dL8#D z$;i(9z?05ET;3#DL`|u4QU&dXhp9laXY+lXLj2nZ4JAxvXZoM3A5VC$n134e+_dls zInu{gYkhN84Paq3v`mX+a4~hQDx^WUPfG5p*Q+wg()rj^vq+VT|7JFY-T!n|VHPh* zJbl7h;V`py^P>{!;~z{O>Bc%IABS7-0tquEc>2n#kYjal`yh{79!hCQC(wS5t3i_7 zA^gVXjNq&WrD2AN;Z(Kqg?6x0moQQxg8j-@mk5lMmgU_PxRd$H}r6E`qOni4FvHG_@t!697jQ z%Tr8#>sQUtCTdk~mh>#c{9zEyEz@d6M@#L@(-@rd6vZq)VlA=unDYl)oXxgO6AL== zJrS0%)!^Tgt2tgX1$jHh#P45Pc1a=JJP`vGQA>)Od^0ztT0n6yT% zO5h^W%W-Gx5IW0B-6wBC2WeX9;z4wdwogt*C$69_!F}(2`~q?&$VVZ<4IIsPGTv7W zxklkdu`Er2OnnAidQbE{ZAEYI91>DSe*RJFn5LiDeknN}zjIs1Ld^MF$zpk0?1|Jv zztOJt-iWy@i2OC>yerW(^_{ zXdK6$_QqjW-dST=gKNRmGf^55?`}^D$%u)S+D5eRpU4WgWFPLHQ#lWI1xiH@BD@n6 zMkfsDH%U`AWH%2W3ex=9tKl~m8|N? zIZ2i`KTms7o1fR6g|dINIpw|UmYvu_#Mk@I*3>*t?l&9EGKg7I!lGOOCM^t&x95Pdc*RfudH5W_#|t@~|8` z%dJ&WubAs+w9Wmy?sya0C?biWXpaYy8ttg(*|tsJ{i)#vY{BsO>yB0e)!vFb>hpXh z0(SfYji^3_d#!o-fG6}kzU^M#TQ(kD#f}rqj>oSMb(>Nb{@|RZJa{L0B^b=xFjU1< zeeqV9Q?PxH=uek%FlwPLMdQPdM;EBdt0luKbK~XFtM>G^h-D5lj9N?si>((yAzeie z$ggI{8FT@bzLzXe^YCG(B;8!MP8U?L$Q{VmSC^+E3`kR;W<}K-ZrJL3ANC|oN+Ngq z4`qXCxq1h5kZeAdP|eFHh9R6RugF3Po_)-c9@-I7d9UhklGWNr)uS&d^DA?FA2^)+ z4kN2tcYXJ{wl-boV-~yBf^&@P1p~sH>?i6n{I`3WN@@W`-|kumyP=7Jxs=U%Op5{6 zwy&W&>q}F*_2t3_5xfnpGQB!xsgq*~SeF2nu_)LqcwLXWhTcc#* zs`h0)k96rvMyHR)t&^{@Ys6I_YSG7Z?5fm%uVRYB7`m$=JM}d4TBSQZ?Fu*F`S-AV zhaDsaHK?1_$ud4_(~gA1!XkV#GI)?k$b6R7*Pm>G6_t~2>m26Fqk(*F#Rym0A}WYRvSgOFsa_@e!SYH`sMnNE4H z?)|$C)H6lgKUqihF`a8A$X{PNKcWkYh>quD(bkLu8-l0fE0>soe$@kc8Q3# zm&9#7G`JN>P=Pbyt@CLo2 z<)Ppjc8RO2!>W?^7v8;}_lXz+-uPo>bY-IpoTojg6WTmg$|`Yf%dbzs6Kt6*D)MO^ z%SdZn4Qt;$nLXM6jsI|~jp7jGHli=pn-m@|uIw%XTJ1;&C{CS;kI{)ry)sXAjnShN z2Odh*($be-9k>L&y?myp*YK*$UUN`1vceZI-#_sfwAc8_ZMA1joImzO&BC5goEj!_ zvw3P^Z`ZuU8<&&ORGmcg6=_NM!*joHV)k}gxwmx-%UL;~qKyYIfei1!V|rLPM%l{k z5~*czF!}Lp+TDl%9Ye-53n_0b#UV74t7zQ%!a>4PXW?^jta?pX_8&n}%ej zuFSM_(z@}T%8DycPNMl0o z<9Ev4BtQVA0)+kMN>`$(l68$Sb3fzY`IZ>fFeXl8HZ4>wMxRIrW! z(Qv=Xa$0o*Cu5WuqNli&VUZZxdpd9|`vMI@iPDpewoVl9Alvh2F4ng=(Tuyq=N{V~v|Rlxa}dT*>lkTko$lok$e7Yo73h0zI%xKB(Rmm4k-=LK(Vt zmw?LI;YT;6N`S$p-G{&RJmKNs8>iWnidt&r)rI-n$O9b(xz@7a70V2ameJmK!svBk zcyE8HGk`;W-^Mmes3?iuqT+=2UYh*MP#0d>blY)|#-)+x+8rl&S%e;%&1cO<)k5>u z{d5kGKfi)5YBC)ihkA(8V{+J*zNRx%E3Df%pE?(lTX--3jWrH}1oFJ)7(T#YuHL!$ zGvbG|LtY~*mzcXkVXX4?#5g^2k8toIN57#sE)pdS%&M8xJT7Q(qRM`yPfYlVxj|EY zia}X4H=6%Oz_NS8nM^Hs2+({gV~MQiZ#Es;%WLnGTuY!FuaVH&_DLq`sHCpF=kvYf z+msK|f4y82dGVMnzb#Xy&)n{wzv6Osd7YonD&ll5Uf8?FVpoG8p7BN3YDHnnYg68! zJycpo_QiL_2( zn`d5EX&k3yT37t$9uez0aUkF5#2F~^=Jg|Gj~m)p4XtGQGJW*WLkf6Gn0mx+Qw?*x zcWc_tN1}R(Y^8$28AQZm{~oGDw2kvzP%)6Tdf9ewIl$cUo#2}t{W}u|VUyS7-}EpQ z-0pmO4K0+<2m`I%lfSH3yp~Rva8k#XtwD?!1S&w^o$S{C_mZF0p6;UaGYv=IQ5``9 zPa3Ml#~$D=G9I3Z2~*32r}tM$>7;B?-~7$Sk-D-S)t@j5itXpnKVck<&lS~*#gVvG znnRfS7<9{{2hMOHJSEY%r;r_Zrq4S< zmM}r5UOxodZG!gCQi$K})~o2+yGSO+p*22qRzdW|pEh4KEL|#KZHHWV$6Wsmcw>mt zZ1a(mEPma~@pUc*y8->o%-wVQx=) z*Ny+{Fyb zeyQn@y#I{%K~N*Oztgr+vdczn{OH#&jXS2fAKhbw`Q707ybG@B2EoeGY7w+hQ-W2~ z;(FzR(7S>=_`JLbX={G=im=V5Fs`Pcx|wHAsBmbI9@(jcugvfdf@YN$$!isMj{*K7 z4$^t+&c!df=%smez0j45Zj6NDi;d(+AL+_ytR10`_X3#@yUZstM~|+;%}?k!qii@U z_u>PY?J%;|7j43;u(xh_S}WEJF3(TQ%dYwSp$>GsCm<@a(1uS1KZ_)BG11FcD@Iho z6*%0q26hZ>&Sw3w=KMyDO#1Gw5q^cG1j;;@oxe$VcT2OobOe&+RpHr=5o;vYu4rtc z^;fy?anG1B5CX+`X(6ZH$J`A`8GclBd-KF1TG44uN){3*&X+s%(X|W_`?e4+#bSe@ z;$z%MGAd}zLAc)Y@r=m4CCaW0ivO_Zm2H~I~g<}Qb3VWWelU6+t^ zy^(Xh{)40t7L#cEE3d3&NJh}tC+QprvqNddHsJZb=8d8mX7fu9I5-y z_YF2G*6rw(Kb_pOgNKDwZ*Hi2I46Fqm2wb!X>%_}j?3ln;JPqejwspED|g1{jS21x zj+=ey-8d(wNQ~2guM6uiT zEu_cvDJ0YR^d{{O;sJmQMJ~6$eEN`Wz=Ro(nXm&7iUT~oIH!ZSM_s0-)`^D|u>j1AB$AQWY z_ds!8+CqgRs00Iy26`Eu_x|3NhV0Ykg@Qdv2*vufB!<)BAu~zc2}HQSx$3Ywkc?_3 zjUUs;@J*@8g_EZtL)U2fS@T_~0{w8$_O{*Og{egqxCyGBP2FkaMDUj4_DD4vy6y~? z2V1*hxT@`q>)WeF#Xu4djDV)q>v;_eUM>>?k-TD64<$~?T2rnEScQ|p`$vAI^}UA?Do9juWc&dXGHJ{o?2YPItG7X)8}=2km}`5NU*>ztfjNnY$K5^c2KNnGmh@B+I%bJt#Ixt0My&AWJ zAw9=XyTvK6z}}**yY$xFw+WB)xRmAaB$@x(psq2R2@}L>CB7;Y`UY z*yb#GBlO`)w~k-8f{e&&%!})yeF$gFny^g1fXcSVvyW~lUfR74-sc7*=akJ`*Q(`) z7R*-;&4Lw=id)L=1HjeWAuNdVQD_lkqmr0H)q)ZlJX2P$Im+8!om&67UA*)_C2V_b zrQYiN^BoZyk~|&Ogqnao`|(~=l)7A=#I+GZ>?uLRLN1J(zwH=?5-slVNx7Z zeke1+Cj^ON-55{X*zPmq@%d_|%Rmx0w(JfbH5?06!eY*>uOtyLG~>J5Oy2BFus$!Y zegw^2ls@_7j%)LNvl6t_*m?(>HLr}IHs%R4AviWC8kos?=-f}Rl~rH5Yd!ttRX*kL z{9Df7uL{u0m+cu?5h+hosMp5d3YF;fY8$jtVIT5RE{OZQL(y7 zym61kVMS?;lbe3$3*aJcce{8;&66~zCXE@HD$aQ+s~(3Cj#zZZZMX`9+1 zC-KfTz2)@VVSWa1)nLZ?{#L{=yVn4y_9<#TIh||v_aR4|-L%JuZBaJAwcaq84R4d7 z=thMl4#@;Ze*0fLMH7M>K^P>c?~IL&kpsY8kJ^yMONLjk9{R3ldX&+3G^~JoXi$7# zE9D~`U4fecJ=yu%aB*-D7M%NAa;y^4?QzNH$Nv2d32T558KfPv4cy%S$Co^4#`b1D zq4W~rv=?zWB;wDG(0qi39|pG7F% z{gOO0Vqs138rqMN2=tf!AoM__SrC>G)||iq7BEkRXy*Ox{)tSsqUx3E8S9X3bDHH7w@f6V93&x9G3tKft_aJzI#acJZ;! z?<~`wAX1g>McH3}ro4bjB;%=E^omK~m$1Xk_YIssvpMSt+DIB+bh4x`7WN3NHrP*^ z80E0M8;9EKJIO8R9GfeC_x7kv7doBS4I-~qGjF5QBlPAUMDCFEUnAkFU*)Bos%Ugm zBVFE>a9~Fu)0Eu0%D}cAts~1d)7R_viRT;k#G*nqC%WI#3H&NRu9lWTvmK!&^O^qC zPNaM|Ltwr36paarzkAAcL$`M|-d;wHeup(H&^1Vv6J?o&L*b8axoT#@+;EX?eI0XA z!|YdkIIlP1)I7bj$@7GwRXev}75}1Gr@xPde>h`tCETEft`~yeYvE_JDjz+GVgI(eWe`3foFW_y6=g6`Q&{j zJ)3h8{j}2-u}LxaxZzxaEIVGr(c#@oWZA6Hy7Nu9%QrOVgVu&06}Z+ib%vAJc>+HQ zm9)@m)lt3?3eN5<2uosG@*;;x1V8sXsV07Sy<@)i^q6%3`@zDAv>=r;`tLUM6tPaE zlvqLpJw-`h>ZnlPZ`p+k9pBC}k$jKs-S6#5O3Mz8De?;fvgYToTde(R@2z+|>F&;@ zSZB7_svoyosc=JcQ-z;~Jz?&6VSPMHOm=R=aY}LLXGdDL_pIW*tmKma!|r8Z^-hw8rWkJv&3qq0t)R-j`C8Q!WT1Ui^p=A>Y!o7T!jY z=Z>;89-*^r!ANTE#r!WAqjOjBf7JwDz(^nBMIGt?4)kCj>gV@tH~8&4Rt~aR|F?gT zWRZ$?Kkbi=3-Y+whi@`gsRyxzpe@A$u*Y9j$cPi6N^`tK?6=m(Rpty7kMC~1kgX7L z>%1%}CjX2h7NpA5Xi!rlUw&1Vv)0-VdB4tusdk=Su2jn7s?!s@8PpW$BIEE}QSVZR z--n6KetxG`#}IrK!B2xI0#U2>r};bHSWdrsVB9dJG%mm1F3iKJ&g;uRL%PnqCR&@7 zj2#fqMy}aF95J=#p~5~+T2edQ(?g{SR}A^Iw|>1Iq(Vft6@p&RkYnZaUFTB0VF}}n zaU8;gIdu0DAUxF?l{ph6>vWqgX$Z%)b11%QNk4{SIO9f}AAfBbV{j+l7D059NC=mP{JXlbz6XVODwK^V5iXAb=HGq!cMAs=WmEbwW4cxsYbFe9{_^!GUtVg283 z#M){$uCck8`8(Z1eeJOMD<@D)$<(BLVf*A*_}T9)Zcj&X2b8$_avGkWq^rF0@k(pz ze&-ifPtw@-PxzxU?EC#OySI+#6{+rDs{mIG&{RH?tu9jld(dlVIg14^BM^?RXGd$x{Sn8;v=?AnS$>6AZ9m|j3VOn zs1@xiHdQ$+AnH} z*@E1jkSf}DaH%Vk(^_DiHz6$6d0B@GUqDpf{4R{hXUv4lj8ug#c)Gz2cZ!5CZVYzMiU|Ao)SR3)X zDh3)K!&|%7kvyvx8U>D5kIyQgClEp5vJgH`++8^hjfyBpp!R+$l(FzMT4ySuR}O(M z;TuJnH(7__{m2EjFpz-+(cFLfI518BV~O(R_)oux*ne9IsQzOmXb$^NIZ9uObbBe{ zTlc^ETINh5{%tyF4s&c4WVQzO@pTKboq_+E>nb$O>__kaTYlhMhSKv78GSkrEyqIB zRPct5W5WOY1^&lx`A;p4E%QI^J4DcE@h|fNrs+3V&D!(x^Lgmc(f|5cde!CEki$C)7qu0e2`3X=h7N%_Ve$g*$%JL%cMdWueUY6(o^ zh0t=vgcWLL4qyV!z4S%n5FN1WqOB+%xlETsB@L4YLy9jVRFIzN*9# zb=SE5?wdm6k!L<;f_KGXtI?moGmIZ_6j9!~Cy=caK15TT(n$@lIrpelb2J?J(s)gT z7fsh#;jED=?VAd3H35C|cjew=!BLb>k9e+5142p&K+ zOwG*nfkg07(Bxai`X~yA^4#;vdv4#g9$Ym@Y!q5Hfg(pjGr-v5;^BR0H@udXmGu$Y zL-(MO?C!0Piups~H_)!VLpatZL+hN-$pRj}1QjzgvzgmF5$|gI<=ykW9u-~P&@qcm z`-0Dk=n_f~g!z&~kl5|{hP4;!_vJk$W#yk$w)3O)Zk13m{;_ zsw!Dl|2`A(j6U%YCmJs3nk?ixcQh^6>K_!X4;|4oQt(4p>FLzm zFGS|~LDBKy=C8|<#zS7cm0bSOm7UkQsOQh9sf8|yer@IGcPFv5zC^6abN8LE=0E(5 z!&n%@wvlQaFcR2UfY~a1B|YGA^ln7f4143ccV_Koy8GrUcL|C83-uQ~rtc2LvC(Pi zpXg8V9X^lqz9vbgV*EqlfXT07X4rRcKGeT@UztSLLk-xUo3P9F5U$F_#l=_dJHOz9 z2fYX&bN9KpVt}lMHc+rL)YS0F(Ww}=MFK$re7LH*Iy=Yz6S^bQiPyh zpn2E7940h4D{!eOaM zcU161_cwnt6)iNJZO+3p^`KFG(rLz{m7m6z=++<2Qs3s(tE3h`3eh(l>FgwILFBTz z7geU0?g^vQ)P?FJj@-eWT>>W?1u2o@I(~*x<&F56r2RHp1 zeA{+nb|0hIemfzbPn+Mn|8RN8XXe9j<@AGt%QI7t12rF>K5fUbH^{d4>})z{wh#L8 z`tQh>n0h5_EqQvJz+dlsmNUM74G9Xuw6n8Ak|02o!kG;I6 zNj-l~kAn_KOM6$>o1@JS77 z(^T3c)B!|4Od#R2*5qP;_*`@N+>iuKe*QeX%h~b~oxuGC@jxZ?J}-bbKp*rEk)wOL z8@qcogkyYTGS&UP?UG#Y%43f$w<_T)GU(R+D;fGMG9iGDjqQ9>L z4%FqGCf}h+T~5rj9WUUydmGJ);-+{BMYJ7b(2KQDo+G@ZFyS2 za{n-FzDb+qy)Ja48zX{&zjOJY7EgkcituVIBVTFWiBUJ zPEfrBJ|z=B)H1ZKt1qw`jJ8r{P;z52{h6x9o8Mnia?^*gbq=16MR0)3W=1OLLl3(} z(C(4meHv4L(Ly9a3=0Ppw3H+D0>6KQJA{^>KM|+^85x=0mA6yfmL?`?Wo7shN)K6C zLEzyFWD0=wrk0lA7!mhX&C1SJ2H%6d{S%hhK)}3UNSVL^fawiJD+vh+672@o`vEt1 ztjF1*Ub)u0ZJhisUl<_@0B!)7o;^Up;LB`8nBgOHj>Lup*6WS ztd}B8DSNg>e$kWMczsPWwB@p2c3u8Cy`q{#CvEI&z5`YLZo%08_tzM^dC}$v^AK@M z-M?HHqocY~x-MhvYd>mUdN((Z z>avBDA@PZyh$VBaDkI6&xiP{qPjy{6?_o0AhhtJ}pJs8e&?B=huO z4I*X#N4k@r=R2y^^Ml&Po~|jGcqD<8unn<)F02b;fUh$;!Fu8gnOnLmYTy@9Z`M@q88#O+Z)rtPOVsv zjFUgU`EiZJ^Qe|(kH0`R2507SfXZ#p$ShkvurB7c3SPqD4K=LbkvoHknj|v(qS)8= zPkh@W*}9V2+je)Qc+d8FoOpX~a?sI)9rnw{J?FjQaAy3Og*!H^=`I5yLNOnEqLl5{ z=!mRb4aN8^RrNV0mX?EAzf4@6w<3w*Pj~yXqPpXr=4O3tv*TLbuGjmuEcOXc8&(|q zrOsKvKIQ|MzWxF9>nx3qC8BKqyIHxinwkW_tpKJ%n3?wQ7UUy_Mn?LT6U1!#*R3i^ zqh*FRB#TBs%L-~Cuy}j}U=J#QdqfrLBR3H1unD$Fh}4uiNJ;tl|1^#G1S z>JDJtfmCI*&gF?#;l1+H*Jxv(?ouP&JHQDdStP_cV7J)~4HW1{Wp6uUiTNEZXk5UK z@JPER3SwAU+1ua|D$s3AfdB@8s?lnDdI)s^yrYPg`2_%PD=>`@+I_SNjjk>)Iukep z0o9HMc!~^5(vbHr#5O?kSN4vMO2NmfT*E`Z4)f+B2>kQ`&Z~p#C5V(egq+_|-1Vam zj^yUygmtI@#a~~YxzS?8S59*&w{anmU1?k`j)~S?p%q6qP=Sv)F6Q z8%!XZIR??j)lR6X=dLvR$4|Uh?pnI87}k1JJKQrto7P^`3yyeh?X=+w-^-2em|_dd z?cqH~PWuy2dT?@!=nX3+eVy~(`9k{@Jg^W01urs%_Ho|WFTJ*4%|I2!LVH?EF0YJR zn7pKPO*O9TZ)+(1MxUgq%O)?PZlZF2y7(QchW5wCIyhov zU~xL$Zo+=IP3ZaIhxeF;5f~=Ir^MA52|yhf7e@qZ7TyXLT47S z^N{wyyo3~&NK$$XF1j?%P*$tMWCcK?69jtxJ z%LC`lcqZuBwz~ubdI^GwA}?Ohm?}w}^x-Kma)?Z{QqI3Bc|V!VzY`lfZ*}FisnbZ& z3Ji0-E%o2GJUU+F%S=VDufJka4+CYIqRX_4kCs-ikx@S!8xvlh7=h{ww4Y2q<#smx z?v7`BPNIm>jA~yPl z87C+Av{VNi<%acqj3aT{NmnN7qpj9@J=Aw!zHIxPE*WX1xP?qwH~i3e6=Xb7(dI%v zf)*QCaj@2IZq-t5N2^AtJ82-d)KoTfA~~>9Vmv^Hb-wL~3Dj(Y*8x+K`AEd^2g=^l ze%ZD{o*nIN$N(iNoPx+m%>5KfhH8=fdcwg5fQ)3FQ4ee%?*w9;-=H${3YE2kVu5upORZGE@$1A%nAMnApW z8oqiwd6IqM`tUmiR`)L{8Uf)9gEx=v%r>X(`TE;zV|j|^e53j)mcqBHj5jSSAwLpb zuIdw+MO0(uo4ck;7ry=Fh2JWX@{p3(vWFH4!Y#)`%e{VmKp$;&MJP(H>xU0R;wBfI9#ZJ3u*gD5Gn)`#b?I&~n6&70g-f~9P(bV7hm6z*+Zd3?!qM$a^)YZL-}a5|0UdDb2dCE7DB)I;g!>r zgLVq|0M3Yn+HdC6u*yRj zU^92HaA3t5Os^=K^{$-q^75}B(Ui<$Mha%Wfh7X@_FztZ0pEnBdSMV$US^h9zD?Tn zwNsvC)X43pEaki!4=nfXA84Z+hn}>LZhm+N_;WG{5YjsKpq>aov!7Cr3z#86v3P-9 zLuj02dlQM>7OI!pFDoEbO7224X_Zgn%Csy)sYzj?zIWgf+B%TqBZ1xrBqpW^gnP)l z7@X{-UV(RD`5hX?eaII86W(bRz6^)TLIfB+pl+d|?LYu1D$<<-qNXQci$Oy22jeXu zNI?NBm2qFXM3UpUDFOSr;5dX57IhCOF96oQHzu+Umbit~t8;oNCYE;d1tJBqOG`#bgcez( zLFzhy+jjo@*$?Wu0&tFyYZFqw2f7~Ms?S`8-0t1_s0n5{SWoC6{f5_Cei&dkdSud- zz=?DbLKz622*nSgGLS0h-xvLigDlA_5qwX`hd4JZX(L&^ja`s>dNQh(^v=H5x?$FnJ$V=?uu?IEYQquuC2-}9UXepr% z2SXxtf4pTe*6*m=v33go-o0;5TRQhA&z^up5ZaPW%*^JXgwZs=v(I1!nMlXtPpiuL zqcadR7%Yr}7mgQ31RF^(ls5Mu3WPv0j@O=U=4|??SgR1C2?~#WC4>U+BL4p<C?37suwe)8Zi46%)jIm&ppu7h6kihwNlUx!{ zI3)hSqvJC;K`Bq~F%h8rSiHQv+N-0WS%c^5K~)lw zfxB!-T?56Q3tY3c(f)yf2GBkZiDozm0wJ+8A1{u=5~uuZdbdA79&B{>_Iwx7U! z0XsQu9Ub#9ti+Y@zgEo!Je9{yFF%3x$B7{l7=NUy2oyM=%H;d9gKql(cBOC}_Hz-S zHUx1ql<<+pi^^%&K#*_WtDFe#Sr#iAIntT|E4UK=M>I-zoY2-LD43I-+&42Z`2#LF zqrqGy_tTY}i&ZM5v=OOESWdFX0@DXvXHbO!xs6eOrZh4$f`OsRs74b4^FlBbcth9M z*W30$nRgD7$2s`fme-Gq;bQFO-xBbavX(=GjJ~UD5jiWQ2eLphX%9YDvCZsHq1LIy zkSe}Pm)!0xC6IW-c`*Qs81e$MoM?e337GtI2KV1MYnq!gA+{Jw4w9__5=jTQ2r#@cYjV# zfB&yK(F0{G__f!sp@7+BYU8$tmKF(=IXSqwQ57L%M2a5|H!?ndeg_K+0!9Q}{}9YW zC5x=upL4XSFTLC)x1|NVrZV2!Qo$L-p*ZyH(pn)AAPqrUEz)2MLtY+PRQvJzaae3@ zD^i>Z&fFqw4exUt`QurS1yau;k?YPDxRJ~usoa}RyGEAA_F?>$Zl@qb5G$!E=!P$ zvVjDLP9docb`Sj(a>O$Fa4>JW6|xEnlEZ`vQ4Xl1<{=yip7aUu_lNzoVAwhUXVzIc z0_nLW;<6yp^*A8VDT;-w5>_WTiT_=3SvY`PAH-G`P>UJQ%E_vzh=En4*mnLI{Cb$u zLOXx)?mvr`$x4T+cAiSkH~0>0+JP2yW1{kicXL|Yd`~gC=&Ub9@)KS>4YNmDpGi8_ zowd58uR~^w({5YAWG}01^agc&NUf@7i!GB~mg2)(VTljN@b+8nsf!a^dCLd#E?kF` zKd}1{29M&2y;vPr26&iOE*C)*Xw~tQ9~GB0wym1hEd%PbzX>#KS`nc2C(rB=XlmK^BDD$b}Ks zqZ}wdR8Eld4v&u`mB#kV-EbOK@?pTiYKBy$K-dPZW%x9L$b0FO7@+;H5(HA>2va{N z@{{p0!`3t`K)dslS43cw)zhN_!y(*Z=4f;?MbNDZ0vT$1sgRt{An%x=`#%z=Kh10X z>v6YBysj=`$Nf*upZB*L9JYoO1D!^`YELcg!A$e_*lPn)T3))1U~>Kn+1eQg@>G6s z$@1%cZLd6V7#th~=P88dcacJ)z0N1C{FqcXr=EN6;@v;RxlCDDl1GS4$?`%U0Wv=4 z`T9RL{{J13{}YA(*Y|P65(+5pNwhAs;r*zzRbEi6z5GU#h5XUx9a`xQDwe80*ZxeL zywN2S?2B=`H#cFm(x27Jf8F*yu^D&rsaftX!NIw^`VYhY0hxHZVYyf|ivOE#Fd#=I z9tWGa`|sT2ap-YRwqC$XRtEnI1QH9He1$_F@#A>wg*x2Q1SxS4{l9fpz%}zhlzcaA zvhKZeD`DJJZQqUcwCflqcsB8SrvAwi20zEH4`t7jn>GHu7A(iWN9f&!cPXogN6O1p za%|SQDY)KM6Welpqm`-o>635g6cLFUC~3JI7Nkc)o|f*Ri;-HxkH4&QhtC`ZAM29f zb_=cAf`9~Pu2#}jtJ#}&e8je&n=nGfMHu5Sl7h9N&A|P#PbkvnnhK6XV&3nc z$BN4DW{nyc&kg0^DUg^&Q#XJ6J;`lG+E087ye&%q^%=z@?%6i$kKbzN+eS}&9h%&pvMzjAHL*5o zXD!0Ck9Gi;Kk@fIDUr?vM;b57PC$cgbCP(FIe*DlQOpLjy3>8DH@Gi{)0EFSUi8rP zUGCU^QbwN38CuBv$x1p`a}*h#*13|peY+fG*5d`-A^XuhVO8*0E1ig~IKJwJ{agG9 zEp> zbMG{AQRKYAc_P(Z8lkmv_=g@ihyd+T|3p3yC z9nbQ;w=zEcTfUDK= zN32)b?!H48A9$9H_Aj5+)u>j4YGZlxEzlOu%HwWO>g~pS#yZ(vi`f1m0)<+Stg;hD;Y6I@Gw!<0LMVWZX{Q4@^a^b`P(F=aLd@1&WTi!$!aPHpg(Pj$5TM zTJEH!dj;cvD~XG(uglYYjy?B zh|%d9=myd?W(shhtgW46U4L0P8! z4`Z31z3Q!NkC(z3w5hU4OgW?t@$Tn^PN=FdVqWY@;82c;;po2O7UIG{F}r?gmhX1DWM4;bpmw!E^I-ZXxYkZ-^_)k3QvovUitu9Lo56 zQ69u{M!?@%CnJ@Gmd17RAVN!srrL#baYZ;V;T4>QJ0jZs*a)?Slb~_0*XGV2!p6lN zelK#gr21=Ei;=#?U7i#-I*4p8BZ$yGK%6~$v@zmlliT7`N~E**NV+TEM0N`TSnNCI zpM>*A_^u^ru14JJGZZ8br9y3FxnY?W9akahJ0cL+uJW^OHw%C1BBPbBpCo3U4KqjJ z!nmwOjw+Y8sz%msz)f?!fi9CvnQMKythwt)QLrRovB5U^{l12KSpx^iib#(BR#ajh znY~i1v6vZiVf(zPJjHX%VPgD(GcS`1%MbnfistFtQNq1RT-6D{m47o z7+tG%59kK1evl_VEwwbO%*-0&D({&8DjgUQbEr9IUV-Uaa2R#Xv8ygJtOdvAYmwIk z>ZxW!!qww$pT6*A(VOJ*6{Z|x*=+7di5_1h$-I!)VEUbkYdF2l2by%Xt5p3YmqQyT zzRmWvlP6rROj1@lC?XQ4%&JbSQzlP>jhwns7h=!J@(u9de6B<|W3EryMQ9BpiiPJD zNN{_C$?P?6!8I^1H+~?te|f6&)i+_PI@EC`*rcMn$)HVNeR8w}zdFDYwTihqG)!-``M7n3K=$)$FcBn11VS z(ql+_J@Q6#vbLnk>3~O-C9}Vj>Bv8~=-2ufnuzwC%Lc~u4=(o_L$a9nLJe9Ffv~@s zFUqv*lrXtQ;k$K8Z~BH(&t?dxx3xEdD>@=FrZ+(#L~y{$V13z(D2nSyt~Pq9tUWjC z{usY*bA=ZH-}Bp+w$!7#Hsry?rDO2d(lEO+qtJ?dJqG7X-p8x%2?I4)7Y7L{wyjvL zz3P4=Niz21gDmw7AwEjSchos9x#mcm?zk4HWf1Dwsj}_n=X@%H7DMDz3HiR|)6KhEA@$en*_qvIrO}MpYU;1MZ zuXop5|K^iK+2d&m-@vas)#IRHS$8y&vP|bYSk;-g{+2RMtGDupYpK^zFr&Wkj&{fp zo!?fQ1Swzi@N`E%z8Bv+g+zY3(UA-+7u)!jw~6z1#a-dBVtI=en7=G4p$w5seK#P0 z@}bCAa|8(?Hl=VQnD2%^SWr-gNQ6nk^xgfzkrwc%y`L-+;tcn%LmEDu+l zADN5Qxc)8HyX9SnUlf4(nWP)N|aT$}Ysoak++*Qk62etBY8TsKMfVTB72lKEd0svq;xqYcTb&*2;DrT7*MBwZ@_P zG#Zk+bE1;?W@7kmB^*022*xO>lDs#&J)S10?D>7!cYM4jxWSdc$B+0RJ7=hfFSQ86 zk-U{mHPJ*$Q%b9;QOeRC#9$BuiIdw67?BNG{(55B+qV;-$Z)G(i3v4ze~ifMCN-A8 zvxp1ogu=bvvo)K9)>$^pt$`D2tn=voQw=xf9OXJeiam2YSAnI0tnGu`oVEf_-2D05 z76(NJ`CjCRN~*xq%-gxr4c8@Ty;gm2`e@ay(_c>|`!TC<7@H1G-;?~A+tx^_Tan$t zCNadeJojhvb`gUuY-zHrp(7qKpDFsCD|Iw;x_@BA7f~`dEj2pgHGQ|8gfIEiB*79b zGA5>s!Q`6G9LX&x)|Of_`VZ;iDz6pi_y`x_V#W8cYnCKzoMj}9)mBtJLg*(RdE*9X z(N&lp-5p{M5j>$Gl*}Arr`_fG>>$Y5E-Wxt!$+6ChV{frJnMR&&nxZU*vFHH9&g@W zwovp89eu78CH#AFo|b(^!Bs9z7(Tqs3M+A@#c1Jm&XP>7k6YqHbh@L3?vmueJI3F* z*N*C2IEl{3udlNW_&d2jbsYLIdx&>h!7W>0ZcL+>CtDMC+PpQ-W{9Ku@_}@7+pgOK zyt7c%L|gn?dy1!cZ*eBneXRG5r!>XOl;~ zj7tCB5qEuywdq>5Tq~QC+sowYU|wXz&|MuJwM4+vHUGHw2uC$BgYG8zp3&T^mr4vz z@uRaxMsJEilGx1O@cnP0h*F+ukZ{KH&LU>>FT8$|DjB_t5j+{yo1FuGSp#(P%BtXK)rHwvcUL$NY?|?kV}Lt(OC~o1f>%Q=KMs z-#q5+GQjvEk05d%k}M>rYIf3)lf^(V=h2gwl1eWP+aX@^euX3TjcjF2h+{$hN^Vd5V-nXhk>N6-@2-vyUzGKC@V!bS=Ob-0AiDb^ zn6dN}1Ck}w`bL!Kxz_bDS)CPaP4d~=!{`c1?78X}5D8<6C@fxNa-QGTPPwc zdBn@!%B?@1&Q!ayo=?*~$P>ay<=f=ndk7lHc!tPe%=Q`YSLI0CPv#xJZI2T;qn70! zksv0yIeS8i*6(@!`*y-Ik(wuV$=3}NQOjHc;hVagC0{Kui(C@kPkBt(mv!%L+5 zI^Q9!TMQ|WhftBhyZ6V#J#D!jDD{mL<#SC73~!vf27;!qI#60;UfQx&6L{VK(LfPd zi7DR}%(w7keY&Y_cP0a){B!PLS$s{=CSX6P(XVzeR>LS$85q6=jP}K-9`L69c|xZc z?ez5M8YAZ$yss!fb;spHzHGj6?dvn|j}nTE8N9Zi5+n6~_{7;`DUSztZ#;D^rHm|4 z6PtcT#CeC(Nsh1l#grt5&p3B7{tOo6>6lWMyAsC^(uNw-q0_n@pHM?lLyr`-NFQTHdo!lO&gnI@S(9rqtFk)`r{1%{Nbk$6$#AnO4StaMY;o=t;gCbP$sq_tN328~$L7BW-Qat|^$bp5-{bC|8JXWL;lYH`$7_Se-8WR@$fJqIYkHUk@0C!5RF}Vx+I1KV zjg#Ej*Tw0LmuA-WYuWl49w*tpK)RFQrqd>g$qcyyiut$h3|hx~?Ol{OTgTT*Yl&c_ zM%{l}NKi@THyjrUYw9Nr;YViVPs`(x8Mw@V8?3Al;Rn(!(4_^p>anEJE&B01qQ7%m zjS?K4uGGU`u=yrJ>oV|%hPW>?g6`QKSQT?TIQT3QBlDv|DN#O16w%aZ|ICap!m ztrTa1jFuy_1LU6Cm#57({nw^qc{dd#Tu!?oIrzFkDX;YU-{mhG|yrAU|zlc;;t9e^myDP~MgGShZQ1zA6Mn_$u&+v19>x=7= z#={1l~5z$T8<( z)cahxi+4eN`^5P}IPFP0)MHOJme=DC_gGr}wk`>y4bnIE=zK|}wVKn=^MavzBc-jp z|5@JS?xusLxORWKfvXin@ck7&G7}NGt&K^89@fO($u6u8Cmz-6VFzs z_WctOddO%YXWTxQm+|K_FLR09cpYBs?Pyx$qFO=FvVApCdvu(;%6~P#72ob**QhJ? z*vCi2o8~?@YuGIrxViaQYP4l-x|Q0iL;9h@ufwUzmbL1!GB5Kt)h7xGZWmr256K?% z^-E~IGka&_??ZFF?S>s~Y&#O^Ke@OYZ`5WOA@Uf_U62x+(x}^=HFc{!Y(a1t?pTP` z_%(OfPPF63^=f5J0Y13qM|76Xnat?c4*a*}U$4NxiBh+DVOHhVov3b9)_H~weJ>lzFB6wc94PIbEIrtv`)4+`{BabtzfMqo(IP)gi}g3pO>S^cxXX&dHZ2 z!@a%trFVnoJneJj+excp$tAVCG4vfLqTN?312!h9Lfg|@?moYRia;D&JSan?etWHv zzZ7+n(mfdF9C#|)PnY`ufj}5gmm%<43L3%;%bT@xPNVFv#EB6I??8gjH;$utKj8Y- zS5d;##N^Mr$l!0peT3+374LO31yKo1f%py|>m=dL?#dKYZQ7>Fb%-w!rc(-}e(dj=me; zM+KH}$Y0dRM&PTQG2IwT985T4jkq~&+WM=tf8>#$!*B6e6ogn+*N<_XW3k2_0*y<* znT@9x^$Axy^BE*wk1Ug)hBJ0u_%4y39_Egx_LQ`g#_VynjvW?BzxX;AU~E{P3ZCR= z3C3kbANG`PpHr9pnRGslcK^%&jq5_mL&?P~?jne$lFs98j@doS2snI$7uXDg>MW5H zc`>F(`3C(nZncTp6-t%zyvMnsHTlt{4xeix@8$DeJ#E~p{ZJ5)cUG<++Q?X1sv_tg zn{V2Zu^T7mOP;THcyw`yv6-hPGdBMvbuUSdBDVkNt5VZ?PL%59VA$n{WD2IX>4zGc z0U^2#5)t@L>&pRoQ;#BBXuC`%VQb8&1w(no~l$c>&fsQe@Z8rQF(su-nd3&8C)@F{%mL_ zr$x^!FZC?7X5snB>S**<(#XZumYd+@={nn!);C^!O-~5=cHfy1o-4D*N7rn5ir%&f z--&y~mvo@U+1jmSXkBQeekC3u?UpT=dqOG9B570h``cCo^8;Bcl`y+CE`|)3#LC7F zT%pwpN?kj@nx_7;!ebY>{fvsv#9>Z+oB-%0CN=KBPy3B5Sn;N<{K@|kTMGW*6 z5A%2G_r6^nEeZa4^4zt??QGb4Ze)niziZdkp%EXK~zjW&AvSFmDu*VXMJoduWj(;fG`Qs}+hSb4g z=gzeouPy37OaCssh|V6iRF)Xl7$oD@klt{s>q!kr`21KhKY($hV_^lGXIRS8n9Qwx zIqE)~wV9K`Lp-yf#G<+1#`p`X!+m9mZy(r}&~aLlI~FLI9~OJiao1&pmTpD08Y(GV zklxxdr|9=8R#?M_qi!-BPT65*9kvio;~=h}W-#gA#d}`XY2_!>-JlY@vqxaezu-ik zQOH)kk)d=zX;GyWz?9M#^5Xf*H&pDDkqfUS0T$V+o>zjO84|QAUz~6j`N7LKUrHuGk?IH6Wx^h1gn-qr!TPVqt5?eNo%9zW9Lg_4c~y-xV&yi4 zTsl8Db;CszEm)x#d}B@M1a*kR@avP-TZ8lsr?g`RfdqpiX}OWs9WjnmkIOM_#1|PW z4mrpAt;3pJ{hc03F5pXyTs$L>RBtoDiY_ z>cXRlpWS+Ag0*1&d)4}LKZgL^waQ%k{#cs$H3#4WK@;qJ`%=uc3N&A*UF)m{<($*=T|B<$Bg&6f_ zYq%Mi{8ir|yZU_jRS$mONG{LIYMQH}z(1FpY$E|BFm=}j6uPKkL<>jA8Syqf8A`h9 zDz#dx@adq?C|Ey=&7;@r`_4(=Ici|k9`o(hikmvq==6P;Nwe;&Y`!N}6pjVHV>3q+ zIl?qg^~ld0(*M4QpYo8I@d!;CmNg7zA?o+qK8FMNuy9`pR-(F**ZVE^@|oVm(|MH1 z&{fK6=V^4roVvV7vh*B*OS3jICaC7hv}p*p&3V(yYi3R4qQ$`Lu!SI>-Zq@w>Egpt z!aW2bVWa>`D)wq6Q_-QCG#!k(znbF*3AXE5rDo=i{EOdw+AXmTQ_L2uFWaX1e!W#Q zJ>5xV=3%G)*8q*}VzT_$fi zaWEAbWgI2Dv>zqJgpbSgf0nhO zUesK5;0rGkw~CZ#xAao8nxfzN>RGN8-tjv7SJ3DDjFKW}p({$~qhG1q0%;@4e{E z$oLF1xc7ajk>x4nn?|qlewnS?qup~RYD>Bed@S}M%bLq(W#vJR!YJ?q!lJjkwI+9T zb1(Y|VYPdax^d6Amffk4ES9&$zQ8}hzUtySf=IZl~`5+}+8@B55{k#otG zH{CmAYp;SUV){1eznh>l^O(mp81WWND!J3F(<6AU%#>sDtLBKigm$S@H@efLJUA|{ zf;!0Nd56Wjcqcut1%;fIy%x7^_52z)4(H?5x2#7RveQ~`{#PJTx1oY1^d)T5Y|%nH zcQf-B>e1fzKGR9BlcD8l#%cQQ;Nvsm>ihj2qH^b>5I?)e9<0fGn>3~D;joVd@o*Bn|czUS1HM;W4xb%uri#yL=JSmKR`hB2sT_Kg7EtE5mq|A_o&%$_}#wy$+SM=ZJyh zSX9jw)yMJQZ(vwx$25HHd%?O$s2b>Ir`=qNfI&{*hoi3?byN!8O8LSKqr#V90zXa@sUyv0FIs7)H z@LZqf<6IWbU0T6ra+u-9*KU+3zwMc)IH5)4L5=#-0*Z_QkvAI!dCg zp3=Lu6tT3g-i$f5-wUU$WRR+MNQ~;M+o-F5W@9^7M0_28+(BH>U!#pUg++Q*LFY@@ z*HyOA`Jgss>q#+U=`bYhN-ACFEf|Re*$0w-snc6v-yE)G^uIL?Zl1vbX)4ExN z)8xTGCJR?t#0rQItk8^U81`PXXs>?`3r%#xvQG6+We3JHM_y{WP)$+<(F@51RnC^K zk))>Q9qW#N=hyj%h=g^25%gfX;r%3T)lr5yj$^C0lJ?H4vp&*<%*kD$-rg7jBca6fMl~Qat<1z?A|9`PqbP0CJ%>mg9^swf+e23N9(eYiqMtpDnQYd44+x>c|nG;C*2K z=KpK&Jj0qww>=*FC;}>oqas`nSU?a#1*L>hlo&clhagJtAiZM)N2Eo{&_s|P2)%}; zsPvxD0tqSuQUcPEmb12=J9o~U^W}WI&wcI}AYmsvd%f?w*1Oiu|F^<~HBH_~f`#~F z+r5Oa-LLre)rXwubuw;smM@+Tm$s)EE(f-5$|#(}O>HJ0CwpK2n1RO2m`A6m)Lg8z z(b-KoYB_y!>JqEp^xkq6taZEpt@-l9w^u4Z5txc07n8Uv#8DUW|JZZf#7B~A42JJc znDCq#qJ60*`6}r|alh%B|4nGJ*xbhAje9ktTi-o_qKp5&2cg!nE>*t0xEX5aCeiKq|=(t(A96;`TVqG+oOZ}2h<#F3o13D zt-3J;)B7ZHlvjyuRuX%EgWcX38KUmMtO#trQyxsIZR9t!;)TsBx>s_v^Q*7Tmm~Y3 zTAYYjV8I8m<^FM>TLmTYktLsJO~ZsE&s}Pw_8t}0Q$Iu3o_r8QJ`$DpAy)Nyc1@4h z=W$l4<%*o1Av)o4GcWdZQNqVq+U?##`knN+oAG2`{DpK+RNYTG%N$5(J=#V!b(5mN z?mUZcBf{Z-_AqW{-JK*S%yO?lIzgc)jHz@tRJ=(wzAN-p)A-Z3jwbH<8P7f2d%tY= zH0^#{h@m*J^=xG^g*whSSP#e@qHsmYI_^BF|LqkS8~8Cyh)tGNZniJJNC>*a25&e8Jr?0MXdl^{5G zx9u}6HesdlSWXwk7TMEU{HUxoGiFyFt=#s%^-whtB2RsoOeW4|rq-i&8L~vb_wlrb z`rHheDc(R^smf7yzFroT{9&qAR%+?+;6=vC3T3WSWt&<8cD%c%O`KK)N(l=7FRP8_ z63vp#j+SN_8?6tXw^?-Ew=t6$H< z!wp7g7b+n6O80hnP}hn4uG{Ga--3@{6s2=dr zaz|Jp;Y~ex0!-&L{-#g1I%C8r4#TdDDZ{=Ey&(%Y{hRB}$|_ zCths%^+VIhO+(R=c(eI(Q$;;ubX7^=6>4OSMabH{eSxj9YD*?J`{-sPBQm5*||a%Oq+J1 zba<+q4pW@)?#sNpd|N@#U+^m$DvoteKXB`VieP$y8T@{kK|NEsv)(4*+v9HcJZ;9? z5Wgh$nOv=w;yvU9_gZ{QZAN2y?xa$!`d_vm=}&uW8zH1|I48w4aITvD+-sEG80fC$ zKcV`2>0^Q4NBEvu`>4vyEo`<=>`zLCqchz;66PCoiZ_{pa6M04qXp(iN!dy3DQk~R zA{|F}L2i5AVDj?5nX8C3tmC%wP1mS;FD!_j<)VW{_LQt?sYtJ{2b&lgQ{>C{167YX zCBfd<*p%DqO}Uf2X_kb2D1VkjsTx;;T&wLHJC>*H-lUO^&ys(Z6Ew>ihl+x_%di7k)ar@! zn3`UEX>+t&eZI3~&VWJ=J*6Fc2ahq=GuRR`n7sd&7|74+GanmxborV>N8s2nv}S5vF1-M_*=*Z8)&W%Kw{i6Z3vxtk!pk zdx>92ZH(w2eop9SXd`!pu=c>e$*n6P6SivG`tXGvtESuFmw47o+uS{=+nvsrv2=le{I&QniCO1lWiAB=wJr%r)wrNlAU8;Dy&vmvbPAGl1c;CJm*7UcqSoU0GZGV~T zRnm7t2Y)~2X-~Z)tz7tHVZ-5b>zigY8~xSA3-F1aAI9h(=o|^VFax8-c&Jm= z;c7m_onOs=nJeeTzFtq>GP69%FbZ)bx_P|e5)4TpvIJHWlQZQrNPl^qUi3Gs92?zb61+X5Z{r9hVK+#w@@-0m_y z`xq*7&jJ53nN1*SU@$@MRA8QX54%zA#_igfTeHkw(82qb=IR!_oh_v^w^s0}B7Sqh zk}h95JqdeJ0ds}--tQasS!mJi(>7raBQ3Zr+_*7woQcLjKN%|qEfQ8Qm4Lm)-<}oeNucnT*#b*G4ZMCZH+Y=F= zYao1_8(4nFp}IOO?m&~qcDvWEl`%jVuWabj(-AlzTF@e9gM9TGkD% ztg?X8NN9R-X5F@>u~F^by%c^rpD+jrbBzFOHVo}}E5TTcxemUHDj7XeThNW#b)8XW z18N?9l3VWJ;GoY~9)ZvUxDFSJmd-@9fqhqRu0e;D3@V^tVs^G~9EeS-b~T_?R8ExC zGGC}mn&|54D%3?_Rr3SU2?;sP%?j9VkUZvqu*~23vdYfuECDRdqfIZgG&E8WwYA*K z1rKR2(61ln<<0B6b7V*4D->$)E9{MHn*O-s;Rym)n4A-mt7~xCrx(m*+Y2s>KE^onJ9k7Fi8g}GJ--vivw8K6nU;?I+&#fAY_ zLvsVjTPpH@Z(inlEt3Qav!Lhh8>>!wLR_=X(CA*$XEh%r&%ULXB_zCO&BRv`o`-C% z4Y5KVXnp14w*9aC*E0My)H>;C?{mV!%FJa4W;&os5S2Q7r1@Cct5@|vX`6%`3eD&z zKsYCpvGn{1V(s=5?nB9-N>vOA2|-f<@8vSxFPe)ZX+w+lIBCzqnvLH@ro#L;UV~(s z^Wec`7&63q002|Ff4?x4SC$AZ%d_+IJJ;EIV?kT{8bor~9T+V*F#tYBi-grIkzVVO zczg<|4TbdzZvb}{zZ)H!4!2K1bDWW*O+c?ybXbA@pXLXwMQb}d%CI{dA6I1O#miA-NGWHW+~Uf3LtsaL!!FTmzP$giYd}rwR@bFhppZ)~wLuVRY>hy~yYI;h_by%UaDOGSY zQ86(Y&>Cas{u5DAQGx;j>NjpYPDx3Dn3dYzu9B-?q5!Sxn;RQ{@<>f15L(tt>OX$< z3;)H|HV0g36)!(u)A+G2z#D<}2(`6zQf@`Xz`E3oZQn&6$el+XB1;HF;YI-aShSCq zbj^nASgPmUf|b{Uj4B;K4EWulO3=;Tz>S0hGK8R@U^l4CDP3L3Rz2ygQaYBFndz!Y z#E|0>*_oM*r0V>9HF&rE9+9WQH#U3_V*&xI-nZtM=;t!^5PNFnG<&5&k$krfOPUr~1T4Ld*U z<1jcsvd|76K1`EcTbQ5Uz7^p=+Ia9uP|y_MLdg+)7=A2#Xqe`efb^iiJ4HuFi{>R) zSK|r_3ecEUjzfp4?zp?SoD~<><_dmK0%&fNH=}icXd!(-A7z-2%!aGnY_kBf%t2^o zlCG1H+C;-FBw&KR_p1?SY*bVl44=bS;5Wc9bWRTxVXC%XmzV3>+1VkX!2X<|AW-b$ zRl|?6z`FpTMFEKmh}(zxj_DhTM|Q#kNmzq;{Eb5he zCJk(}vz%5V96YLtl4&40KPxDx5D^hk3CIBmYJamDX?zD^gOi&(2Nnt}oNS0m+61kV zl9KxN-(4hK!SUmKmndkACU~Sk+vCB&*jZX$o~)cA-qzLq4RNb#Y9k9i;Pvb)Cr*O_ zk*EgFB@1XR_0IYo9wV^nKl1zyMl1D!dk}1+#)^c|Xd_V6A9s6RM)#wR7u2erT(DT0 zq#&-VqZCN4))3R>2JHt5->)x8%~pM07y=o*56}4M=ogp}JZ>~t$(ySUvOLS*ig0kL z(+Ktz2b>LB15X&HLzY`|TUtOWYLiF8YH~Fvc6|L_4BcUdroRt6h zTU{#bQagAF`;bJ31@n)D2cwTJ@R+-o#Dwe(bzwJBapM^2OqTnR-CvPG&ilVLJ&G-K vW|8K=A-Ohce*-^u=Mm&Dd6(kC)PTH9x2n3l^ewC#t;7vV<8J2 literal 0 HcmV?d00001 diff --git a/frame_place.go b/frame_place.go index e715358..42a1f59 100644 --- a/frame_place.go +++ b/frame_place.go @@ -54,6 +54,8 @@ func (w *Frame) Place(child Widget, config Place) { func (w *Frame) computePlaced(e render.Engine) { var ( frameSize = w.BoxSize() + // maxWidth int + // maxHeight int ) for _, row := range w.placed { diff --git a/go.mod b/go.mod index 6c9025f..bafabde 100644 --- a/go.mod +++ b/go.mod @@ -1,9 +1,9 @@ module git.kirsle.net/go/ui -go 1.13 +go 1.16 require ( - git.kirsle.net/go/render v0.0.0-20210104010442-b4a1979a8ba1 - github.com/veandco/go-sdl2 v0.4.7 // indirect - golang.org/x/image v0.0.0-20210504121937-7319ad40d33e // indirect + git.kirsle.net/go/render v0.0.0-20210614025954-d77f5056b782 + github.com/veandco/go-sdl2 v0.4.8 // indirect + golang.org/x/image v0.0.0-20210628002857-a66eb6448b8d ) diff --git a/go.sum b/go.sum index 277cb31..4ecbbe9 100644 --- a/go.sum +++ b/go.sum @@ -1,166 +1,17 @@ -cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.37.4/go.mod h1:NHPJ89PdicEuT9hdPXMROBD91xc5uRDxsMtSB16k7hw= -git.kirsle.net/go/render v0.0.0-20200102014411-4d008b5c468d h1:vErak6oVRT2dosyQzcwkjXyWQ2NRIVL8q9R8NOUTtsg= -git.kirsle.net/go/render v0.0.0-20200102014411-4d008b5c468d/go.mod h1:ywZtC+zE2SpeObfkw0OvG01pWHQadsVQ4WDKOYzaejc= git.kirsle.net/go/render v0.0.0-20210104010442-b4a1979a8ba1 h1:wGQLjBnWvqx7rU43yFG8ow4rnYqUMUwqorEkxdPaJ6Q= git.kirsle.net/go/render v0.0.0-20210104010442-b4a1979a8ba1/go.mod h1:ss7pvZbGWrMaDuZwyUTjV9+T0AJwAkxZZHwMFsvHrkk= -github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo= -github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI= -github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= -github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= -github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= -github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= -github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/denisenkom/go-mssqldb v0.0.0-20190515213511-eb9f6a1743f3/go.mod h1:zAg7JM8CkOJ43xKXIj7eRO9kmWm/TW578qo+oDO6tuM= -github.com/disintegration/imaging v1.6.0/go.mod h1:xuIt+sRxDFrHS0drzXUlCJthkJ8k7lkkUojDSR247MQ= -github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs= -github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU= -github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= -github.com/edwvee/exiffix v0.0.0-20180602190213-b57537c92a6b/go.mod h1:KoE3Ti1qbQXCb3s/XGj0yApHnbnNnn1bXTtB5Auq/Vc= -github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5/go.mod h1:a2zkGnVExMxdzMo3M0Hi/3sEU+cWnZpSni0O6/Yb/P0= -github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= -github.com/garyburd/redigo v1.6.0/go.mod h1:NR3MbYisc3/PwhQ00EMzDiPmrwpPxAn5GI05/YaO1SY= -github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= -github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= -github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= -github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= -github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= -github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= -github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= -github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= -github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= -github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= -github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= -github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= -github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= -github.com/gorilla/feeds v1.1.1/go.mod h1:Nk0jZrvPFZX1OBe5NPiddPw7CfwF6Q9eqzaBbaightA= -github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= -github.com/gorilla/mux v1.7.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= -github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4= -github.com/gorilla/sessions v1.1.3/go.mod h1:8KCfur6+4Mqcc6S0FEfKuN15Vl5MgXW92AE8ovaJD0w= -github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= -github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= -github.com/jinzhu/gorm v1.9.9/go.mod h1:Kh6hTsSGffh4ui079FHrR5Gg+5D0hgihqDcsDN2BBJY= -github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= -github.com/jinzhu/now v1.0.1/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= -github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= -github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= -github.com/kirsle/blog v0.0.0-20191022175051-d78814b9c99b/go.mod h1:D6I6jquqhLewuhrVt9Q3p97r2YCC7CA4DgNv14+WS8k= -github.com/kirsle/golog v0.0.0-20180411020913-51290b4f9292/go.mod h1:0KaOvOX8s5YINMREeyTILsuU0wkmnKQQTy99e/2oDGc= -github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= -github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= -github.com/lib/pq v1.1.1/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= -github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= -github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= -github.com/microcosm-cc/bluemonday v1.0.2/go.mod h1:iVP4YcDBq+n/5fb23BhYFvIMq/leAFZyRl6bYmGDlGc= -github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= -github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8= -github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= -github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw= -github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= -github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= -github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs= -github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= -github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= -github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= -github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= -github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= -github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= -github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= -github.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd/go.mod h1:hPqNNc0+uJM6H+SuU8sEs5K5IQeKccPqeSjfgcKGgPk= -github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= -github.com/shurcooL/github_flavored_markdown v0.0.0-20181002035957-2122de532470/go.mod h1:2dOwnU2uBioM+SGy2aZoq1f/Sd1l9OkAeAUvjSyvgU0= -github.com/shurcooL/highlight_diff v0.0.0-20181222201841-111da2e7d480/go.mod h1:ZpfEhSmds4ytuByIcDnOLkTHGUI6KNqRNPDLHDk+mUU= -github.com/shurcooL/highlight_go v0.0.0-20181215221002-9d8641ddf2e1/go.mod h1:UDKB5a1T23gOMUJrI+uSuH0VRDStOiUVSjBTRDVBVag= -github.com/shurcooL/octicon v0.0.0-20181222203144-9ff1a4cf27f4/go.mod h1:eWdoE5JD4R5UVWDucdOPg1g2fqQRq78IQa9zlOV1vpQ= -github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= -github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= -github.com/sourcegraph/annotate v0.0.0-20160123013949-f4cad6c6324d/go.mod h1:UdhH50NIW0fCiwBSr0co2m7BnFLdv4fQTgdqdJTHFeE= -github.com/sourcegraph/syntaxhighlight v0.0.0-20170531221838-bd320f5d308e/go.mod h1:HuIsMU8RRBOtsCgI77wP899iHVBQpCmg4ErYMZB+2IA= -github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= -github.com/tomnomnom/xtermcolor v0.0.0-20160428124646-b78803f00a7e h1:Ee+VZw13r9NTOMnwTPs6O5KZ0MJU54hsxu9FpZ4pQ10= -github.com/tomnomnom/xtermcolor v0.0.0-20160428124646-b78803f00a7e/go.mod h1:fSIW/szJHsRts/4U8wlMPhs+YqJC+7NYR+Qqb1uJVpA= -github.com/urfave/negroni v1.0.0/go.mod h1:Meg73S6kFm/4PpbYdq35yYWoCZ9mS/YSx+lKnmiohz4= -github.com/veandco/go-sdl2 v0.4.1 h1:HmSBvVmKWI8LAOeCfTTM8R33rMyPcs6U3o8n325c9Qg= +git.kirsle.net/go/render v0.0.0-20210614025954-d77f5056b782 h1:Ko+NvZxmJbW+M1dA2jCSnV6qSpkLoZASramE6ltlf/s= +git.kirsle.net/go/render v0.0.0-20210614025954-d77f5056b782/go.mod h1:ss7pvZbGWrMaDuZwyUTjV9+T0AJwAkxZZHwMFsvHrkk= github.com/veandco/go-sdl2 v0.4.1/go.mod h1:FB+kTpX9YTE+urhYiClnRzpOXbiWgaU3+5F2AB78DPg= github.com/veandco/go-sdl2 v0.4.7 h1:VfpCM+LfEGDbHdByglCo2bcBsevjFvzl8W0f6VLNitg= github.com/veandco/go-sdl2 v0.4.7/go.mod h1:OROqMhHD43nT4/i9crJukyVecjPNYYuCofep6SNiAjY= -go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= -golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20190618222545-ea8f1a30c443 h1:IcSOAf4PyMp3U3XbIEj1/xJ2BjNN2jWv7JoyOsMxXUU= -golang.org/x/crypto v0.0.0-20190618222545-ea8f1a30c443/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs= -golang.org/x/image v0.0.0-20200119044424-58c23975cae1 h1:5h3ngYt7+vXCDZCup/HkCQgW5XwmSvR/nA2JmJ0RErg= +github.com/veandco/go-sdl2 v0.4.8 h1:A26KeX6R1CGt/BQGEov6oxYmVGMMEWDVqTvK1tXvahE= +github.com/veandco/go-sdl2 v0.4.8/go.mod h1:OROqMhHD43nT4/i9crJukyVecjPNYYuCofep6SNiAjY= golang.org/x/image v0.0.0-20200119044424-58c23975cae1/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/image v0.0.0-20210504121937-7319ad40d33e h1:PzJMNfFQx+QO9hrC1GwZ4BoPGeNGhfeQEgcQFArEjPk= golang.org/x/image v0.0.0-20210504121937-7319ad40d33e/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= -golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= -golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= -golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= -golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190412213103-97732733099d h1:+R4KGOnez64A81RvjARKc4UT5/tI9ujCIVX+P5KiHuI= -golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/image v0.0.0-20210628002857-a66eb6448b8d h1:RNPAfi2nHY7C2srAV8A49jpsYr0ADedCk1wq6fTMTvs= +golang.org/x/image v0.0.0-20210628002857-a66eb6448b8d/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= -golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk= -google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= -google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= -google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190404172233-64821d5d2107/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= -google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= -gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= -gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df/go.mod h1:LRQQ+SO6ZHR7tOkpBDuZnXENFzX8qRjMDMyPD6BRkCw= -gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= -gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= diff --git a/tabframe.go b/tabframe.go new file mode 100644 index 0000000..b98804a --- /dev/null +++ b/tabframe.go @@ -0,0 +1,282 @@ +package ui + +import ( + "fmt" + + "git.kirsle.net/go/render" + "git.kirsle.net/go/ui/style" + "git.kirsle.net/go/ui/theme" +) + +// TabFrame is a tabbed notebook of multiple frames showing +// tab names along the top and clicking them reveals each +// named tab. +type TabFrame struct { + Name string + Frame + + supervisor *Supervisor + style *style.Button + + // Child widgets. + header *Frame + content *Frame + tabButtons []*Button + tabFrames []*Frame + currentTabKey string +} + +// NewTabFrame creates a new Frame. +func NewTabFrame(name string) *TabFrame { + w := &TabFrame{ + Name: name, + style: Theme.TabFrame, + header: NewFrame(name + " Header"), + content: NewFrame(name + " Content"), + tabButtons: []*Button{}, + tabFrames: []*Frame{}, + } + + // Initialize the root frame of this widget. + w.Frame.Setup() + + // Pack the high-level layout into the root frame. + // Only root needs to Present for this widget. + w.Frame.Pack(w.header, Pack{ + Side: N, + FillX: true, + }) + w.Frame.Pack(w.content, Pack{ + Side: N, + Fill: true, + Expand: true, + }) + + w.SetBackground(render.RGBA(1, 0, 0, 0)) // invisible default BG + w.IDFunc(func() string { + return fmt.Sprintf("TabFrame<%s>", + name, + ) + }) + + return w +} + +// AddTab creates a new content tab. The key is a unique identifier +// for the tab and is how the TabFrame knows which tab is selected. +// +// The child widget would probably be a Label or Image but could be +// any other kind of widget. +// +// The first tab added becomes the selected tab by default. +func (w *TabFrame) AddTab(key string, child Widget) *Frame { + // Create the tab button for this tab. + button := NewButton(key, child) + button.SetStyle(w.style) + button.FixedColor = true + button.SetOutlineSize(0) + button.SetBorderSize(1) + w.setButtonStyle(button, len(w.tabButtons) == 0) + w.header.Pack(button, Pack{ + Side: W, + }) + + button.Handle(MouseDown, func(ed EventData) error { + w.SetTab(key) + return nil + }) + + // Create the frame of the tab's body. + frame := NewFrame(key) + frame.Configure(Config{ + BorderSize: w.style.BorderSize, + Background: w.style.Background, + BorderStyle: BorderRaised, + }) + if len(w.tabFrames) > 0 { + frame.Hide() + } + + // Pack this frame into the content part of the widget. + w.content.Pack(frame, Pack{ + Side: N, + FillX: true, + }) + + w.tabButtons = append(w.tabButtons, button) + w.tabFrames = append(w.tabFrames, frame) + return frame +} + +// set the tab style between active and inactive +func (w *TabFrame) setButtonStyle(button *Button, active bool) { + var style = button.GetStyle() + if active { + button.SetBackground(style.Background) + button.SetBorderStyle(BorderRaised) + if label, ok := button.child.(*Label); ok { + label.Font.Color = style.Foreground + } + } else { + button.SetBackground(style.Background.Darken(theme.BorderColorOffset)) + button.SetBorderStyle(BorderSolid) + if label, ok := button.child.(*Label); ok { + label.Font.Color = style.Foreground + } + } +} + +// SetTab changes the selected tab to the new value. If the +// tab doesn't exist, the first tab is selected. +func (w *TabFrame) SetTab(key string) bool { + var found bool + for i, frame := range w.tabFrames { + button := w.tabButtons[i] + if frame.Name == key { + frame.Show() + w.setButtonStyle(button, true) + w.currentTabKey = key + found = true + } else { + frame.Hide() + w.setButtonStyle(button, false) + } + } + + if !found && len(w.tabFrames) > 0 { + w.tabFrames[0].Show() + w.currentTabKey = w.tabFrames[0].Name + } + + return found +} + +// Supervise activates the tab frame using your supervisor. If you +// don't call this, the tab buttons won't be clickable! +// +// Call this AFTER adding all tabs. This function calls Supervisor.Add +// on all tab buttons. +func (w *TabFrame) Supervise(supervisor *Supervisor) { + for _, button := range w.tabButtons { + supervisor.Add(button) + } +} + +// SetStyle controls the visual styling of the tab button bar. +func (w *TabFrame) SetStyle(style *style.Button) { + w.style = style + for _, button := range w.tabButtons { + button.SetStyle(style) + w.setButtonStyle(button, !button.Hidden()) + } +} + +// Compute the size of the Frame. +func (w *TabFrame) Compute(e render.Engine) { + // Compute all the child frames. + w.Frame.Compute(e) + + // Call the BaseWidget Compute in case we have subscribers. + w.BaseWidget.Compute(e) +} + +// Present the Frame. +func (w *TabFrame) Present(e render.Engine, P render.Point) { + if w.Hidden() { + return + } + + var ( + S = w.Size() + ) + + // Draw the widget's border and everything. + w.DrawBox(e, P) + + // Draw the background color. + e.DrawBox(w.Background(), render.Rect{ + X: P.X + w.BoxThickness(1), + Y: P.Y + w.BoxThickness(1), + W: S.W - w.BoxThickness(2), + H: S.H - w.BoxThickness(2), + }) + + // Present the root frame. + w.Frame.Present(e, P) + + // Draw the borders over the tabs. + w.presentBorders(e, P) + + // Call the BaseWidget Present in case we have subscribers. + w.BaseWidget.Present(e, P) +} + +/* +presentBorders handles drawing the borders around tab buttons. + +The tabs are simple Button widgets but drawn with no borders. Instead, +borders are painted on post-hoc in the Present function. +*/ +func (w *TabFrame) presentBorders(e render.Engine, P render.Point) { + if len(w.tabButtons) == 0 { + return + } + + // Prep some variables. + var ( + // The 1st and last tab button widgets. + first = w.tabButtons[0] + last = w.tabButtons[len(w.tabButtons)-1] + topLeft = AbsolutePosition(first) + bottomRight = AbsolutePosition(last) + + // The absolute bounding box of the tabs part of the UI, + // from the top-left corner of Tab #1 to the bottom-right + // corner of the final tab. + bounding = render.Rect{ + X: P.X, //topLeft.X + first.BoxThickness(4), + Y: P.Y, //topLeft.Y + first.BoxThickness(4), + W: bottomRight.X + last.Size().W - topLeft.X, + H: bottomRight.Y + last.Size().H - topLeft.Y, + } + + // The very bottom edge of the whole tab bar, + // to overlap the BorderSize=1 along their buttons. + bottomLine = []render.Point{ + render.NewPoint(P.X+1, bounding.Y+bounding.H-1), + render.NewPoint(bounding.X+bounding.W-1, bounding.Y+bounding.H-1), + } + ) + + // Draw a shadow border on all the inactive tabs' right edges, + // so they don't all blend together in solid grey. + // Note: the active button has a BorderSize=1 and others are 0. + for i, button := range w.tabButtons { + if button.Name != w.currentTabKey { + // If it immediately precedes the current tab, do not draw the line, + // it would cover the highlight color of the current tab's button. + if i+1 < len(w.tabButtons) && w.tabButtons[i+1].Name == w.currentTabKey { + continue + } + + var ( + abs = AbsolutePosition(button) + size = button.BoxSize() + points = []render.Point{ + render.NewPoint(abs.X+size.W-1, abs.Y+2), + render.NewPoint(abs.X+size.W-1, abs.Y+size.H-2), + } + ) + e.DrawLine(button.Background().Darken(theme.BorderColorOffset), points[0], points[1]) + } + } + + // Erase the button edge from all tabs. + e.DrawLine(w.style.Background, bottomLine[0], bottomLine[1]) + e.DrawBox(w.style.Background, render.Rect{ + X: bottomLine[0].X + 1, + Y: bottomLine[0].Y, + W: bounding.W - 2, + H: 3, + }) +} diff --git a/theme/theme.go b/theme/theme.go index eabcfa3..71afe0c 100644 --- a/theme/theme.go +++ b/theme/theme.go @@ -17,19 +17,21 @@ var ( // Theme is a collection of styles for various built-in widgets. type Theme struct { - Name string - Window *style.Window - Label *style.Label - Button *style.Button - Tooltip *style.Tooltip + Name string + Window *style.Window + Label *style.Label + Button *style.Button + Tooltip *style.Tooltip + TabFrame *style.Button } // Default theme. var Default = Theme{ - Name: "Default", - Label: &style.DefaultLabel, - Button: &style.DefaultButton, - Tooltip: &style.DefaultTooltip, + Name: "Default", + Label: &style.DefaultLabel, + Button: &style.DefaultButton, + Tooltip: &style.DefaultTooltip, + TabFrame: &style.DefaultButton, } // DefaultFlat is a flat version of the default theme. @@ -45,6 +47,16 @@ var DefaultFlat = Theme{ BorderStyle: style.BorderSolid, BorderSize: 2, }, + TabFrame: &style.Button{ + Background: style.DefaultButton.Background, + Foreground: style.DefaultButton.Foreground, + OutlineColor: style.DefaultButton.OutlineColor, + OutlineSize: 1, + HoverBackground: style.DefaultButton.HoverBackground, + HoverForeground: style.DefaultButton.HoverForeground, + BorderStyle: style.BorderSolid, + BorderSize: 2, + }, } // DefaultDark is a dark version of the default theme. @@ -74,4 +86,13 @@ var DefaultDark = Theme{ Background: render.RGBA(60, 60, 60, 230), Foreground: render.Cyan, }, + TabFrame: &style.Button{ + Background: render.DarkGrey, + Foreground: render.Grey, + OutlineColor: render.DarkGrey, + OutlineSize: 1, + HoverBackground: render.Grey, + BorderStyle: style.BorderRaised, + BorderSize: 2, + }, } diff --git a/window.go b/window.go index a9dbd7a..22340a7 100644 --- a/window.go +++ b/window.go @@ -341,7 +341,7 @@ func (w *Window) Pack(child Widget, config ...Pack) { // Place a child widget into the window's main frame. func (w *Window) Place(child Widget, config Place) { - w.content.Place(child, config) + w.body.Place(child, config) } // TitleBar returns the title bar widgets. @@ -356,6 +356,14 @@ func (w *Window) Configure(C Config) { w.body.Configure(C) // Don't pass dimensions down any further than the body. + // TODO: this causes the content frame to compute its size + // dynamically based on Packed widgets, but if using Place on + // your window, the content frame doesn't know a size by which + // to place the child relative to (Frame has size 0x0). + // Commenting out these two lines causes windows to render very + // incorrectly (child frame content flying off the window bottom). + // In the meantime, Window.Place intercepts it and draws it onto + // the parent window directly so it works how you expect. C.Width = 0 C.Height = 0 w.content.Configure(C) @@ -366,6 +374,11 @@ func (w *Window) ConfigureTitle(C Config) { w.titleBar.Configure(C) } +// ContentFrame returns the main content Frame of this window. +func (w *Window) ContentFrame() *Frame { + return w.content +} + // Compute the window. func (w *Window) Compute(e render.Engine) { w.engine = e // hang onto it in case of maximize