From 1aea308645585f2fbc6c8b170381c811d562cc99 Mon Sep 17 00:00:00 2001 From: Pietro Gagliardi Date: Sun, 3 Aug 2014 09:18:35 -0400 Subject: [PATCH] Set up the Control restructure and migrated the Windows implementation over. Lots of repetition, but hopefully more correct and maintainable! --- redo/button_windows.go | 38 +++++++++++++++++------- redo/checkbox_windows.go | 56 ++++++++++++++++++++++++++++++----- redo/control.go | 62 ++++++--------------------------------- redo/control_windows.go | 44 ++++++++++++++------------- redo/label_windows.go | 32 +++++++++++++++----- redo/tab_windows.go | 62 +++++++++++++++++++++++---------------- redo/table_windows.go | 45 +++++++++++++++++++++------- redo/textfield_windows.go | 27 +++++++++++++++-- 8 files changed, 229 insertions(+), 137 deletions(-) diff --git a/redo/button_windows.go b/redo/button_windows.go index aa7a60a..5deb53d 100644 --- a/redo/button_windows.go +++ b/redo/button_windows.go @@ -16,9 +16,9 @@ type button struct { var buttonclass = toUTF16("BUTTON") -func startNewButton(text string, style C.DWORD) *button { +func newButton(text string) *button { c := newControl(buttonclass, - style | C.WS_TABSTOP, + C.BS_PUSHBUTTON | C.WS_TABSTOP, 0) c.setText(text) C.controlSetControlFont(c.hwnd) @@ -26,13 +26,7 @@ func startNewButton(text string, style C.DWORD) *button { controlbase: c, clicked: newEvent(), } - return b -} - -func newButton(text string) *button { - b := startNewButton(text, C.BS_PUSHBUTTON) C.setButtonSubclass(b.hwnd, unsafe.Pointer(b)) - b.fpreferredSize = b.buttonpreferredSize return b } @@ -55,13 +49,29 @@ func buttonClicked(data unsafe.Pointer) { println("button clicked") } +func (b *button) setParent(p *controlParent) { + basesetParent(b.controlbase, p) +} + +func (b *button) containerShow() { + basecontainerShow(b.controlbase) +} + +func (b *button) containerHide() { + basecontainerHide(b.controlbase) +} + +func (b *button) allocate(x int, y int, width int, height int, d *sizing) []*allocation { + return baseallocate(b, x, y, width, height, d) +} + const ( // from http://msdn.microsoft.com/en-us/library/windows/desktop/dn742486.aspx#sizingandspacing buttonHeight = 14 ) -func (b *button) buttonpreferredSize(d *sizing) (width, height int) { - // common controls 6 thankfully provides a method to grab this... +func (b *button) preferredSize(d *sizing) (width, height int) { + // comctl32.dll version 6 thankfully provides a method to grab this... var size C.SIZE size.cx = 0 // explicitly ask for ideal size @@ -75,3 +85,11 @@ println("message failed; falling back") xmargins := 2 * int(C.GetSystemMetrics(C.SM_CXEDGE)) return xmargins + int(b.textlen), fromdlgunitsY(buttonHeight, d) } + +func (b *button) commitResize(a *allocation, d *sizing) { + basecommitResize(b.controlbase, a, d) +} + +func (b *button) getAuxResizeInfo(d *sizing) { + basegetAuxResizeInfo(d) +} diff --git a/redo/checkbox_windows.go b/redo/checkbox_windows.go index 633f8e7..2dcf519 100644 --- a/redo/checkbox_windows.go +++ b/redo/checkbox_windows.go @@ -10,20 +10,38 @@ import ( import "C" type checkbox struct { - *button + *controlbase + toggled *event } func newCheckbox(text string) *checkbox { + // don't use BS_AUTOCHECKBOX here because it creates problems when refocusing (see http://blogs.msdn.com/b/oldnewthing/archive/2014/05/22/10527522.aspx) + // we'll handle actually toggling the check state ourselves (see controls_windows.c) + cc := newControl(buttonclass, + C.BS_CHECKBOX | C.WS_TABSTOP, + 0) + cc.setText(text) + C.controlSetControlFont(cc.hwnd) c := &checkbox{ - // don't use BS_AUTOCHECKBOX here because it creates problems when refocusing (see http://blogs.msdn.com/b/oldnewthing/archive/2014/05/22/10527522.aspx) - // we'll handle actually toggling the check state ourselves (see controls_windows.c) - button: startNewButton(text, C.BS_CHECKBOX), + controlbase: cc, + toggled: newEvent(), } - c.fpreferredSize = c.checkboxpreferredSize C.setCheckboxSubclass(c.hwnd, unsafe.Pointer(c)) return c } +func (c *checkbox) OnToggled(e func()) { + c.toggled.set(e) +} + +func (c *checkbox) Text() string { + return c.text() +} + +func (c *checkbox) SetText(text string) { + c.setText(text) +} + func (c *checkbox) Checked() bool { if C.checkboxChecked(c.hwnd) == C.FALSE { return false @@ -42,10 +60,26 @@ func (c *checkbox) SetChecked(checked bool) { //export checkboxToggled func checkboxToggled(data unsafe.Pointer) { c := (*checkbox)(data) - c.clicked.fire() + c.toggled.fire() println("checkbox toggled") } +func (c *checkbox) setParent(p *controlParent) { + basesetParent(c.controlbase, p) +} + +func (c *checkbox) containerShow() { + basecontainerShow(c.controlbase) +} + +func (c *checkbox) containerHide() { + basecontainerHide(c.controlbase) +} + +func (c *checkbox) allocate(x int, y int, width int, height int, d *sizing) []*allocation { + return baseallocate(c, x, y, width, height, d) +} + const ( // from http://msdn.microsoft.com/en-us/library/windows/desktop/dn742486.aspx#sizingandspacing checkboxHeight = 10 @@ -53,7 +87,15 @@ const ( checkboxXFromLeftOfBoxToLeftOfLabel = 12 ) -func (c *checkbox) checkboxpreferredSize(d *sizing) (width, height int) { +func (c *checkbox) preferredSize(d *sizing) (width, height int) { return fromdlgunitsX(checkboxXFromLeftOfBoxToLeftOfLabel, d) + int(c.textlen), fromdlgunitsY(checkboxHeight, d) } + +func (c *checkbox) commitResize(a *allocation, d *sizing) { + basecommitResize(c.controlbase, a, d) +} + +func (c *checkbox) getAuxResizeInfo(d *sizing) { + basegetAuxResizeInfo(d) +} diff --git a/redo/control.go b/redo/control.go index 0b88533..953e241 100644 --- a/redo/control.go +++ b/redo/control.go @@ -13,57 +13,13 @@ type Control interface { controlSizing } -// All Controls on the backend (that is, everything except Stack and Grid) embed this structure, which provides the Control interface methods. -// If a Control needs to override one of these functions, it assigns to the function variables. -type controldefs struct { - fsetParent func(p *controlParent) - fcontainerShow func() - fcontainerHide func() - fallocate func(x int, y int, width int, height int, d *sizing) []*allocation - fpreferredSize func(*sizing) (int, int) - fcommitResize func(*allocation, *sizing) - fgetAuxResizeInfo func(*sizing) -} - -// There's no newcontroldefs() function; all defaults are set by controlbase. - -func (w *controldefs) setParent(p *controlParent) { - w.fsetParent(p) -} - -func (w *controldefs) containerShow() { - w.fcontainerShow() -} - -func (w *controldefs) containerHide() { - w.fcontainerHide() -} - -func (w *controldefs) allocate(x int, y int, width int, height int, d *sizing) []*allocation { - return w.fallocate(x, y, width, height, d) -} - -func (w *controldefs) preferredSize(d *sizing) (int, int) { - return w.fpreferredSize(d) -} - -func (w *controldefs) commitResize(c *allocation, d *sizing) { - w.fcommitResize(c, d) -} - -func (w *controldefs) getAuxResizeInfo(d *sizing) { - w.fgetAuxResizeInfo(d) -} - -// and this is the same across all platforms -func baseallocate(c *controlbase) func(x int, y int, width int, height int, d *sizing) []*allocation { - return func(x int, y int, width int, height int, d *sizing) []*allocation { - return []*allocation{&allocation{ - x: x, - y: y, - width: width, - height: height, - this: c, - }} - } +// this is the same across all platforms +func baseallocate(c Control, x int, y int, width int, height int, d *sizing) []*allocation { + return []*allocation{&allocation{ + x: x, + y: y, + width: width, + height: height, + this: c, + }} } diff --git a/redo/control_windows.go b/redo/control_windows.go index b2af110..fd0ccaa 100644 --- a/redo/control_windows.go +++ b/redo/control_windows.go @@ -6,7 +6,6 @@ package ui import "C" type controlbase struct { - *controldefs hwnd C.HWND parent C.HWND // for Tab and Group textlen C.LONG @@ -18,29 +17,34 @@ type controlParent struct { func newControl(class C.LPWSTR, style C.DWORD, extstyle C.DWORD) *controlbase { c := new(controlbase) + // TODO rename to newWidget c.hwnd = C.newWidget(class, style, extstyle) - c.controldefs = new(controldefs) - c.fsetParent = func(p *controlParent) { - C.controlSetParent(c.hwnd, p.hwnd) - c.parent = p.hwnd - } - c.fcontainerShow = func() { - C.ShowWindow(c.hwnd, C.SW_SHOW) - } - c.fcontainerHide = func() { - C.ShowWindow(c.hwnd, C.SW_HIDE) - } - c.fallocate = baseallocate(c) - // don't specify c.fpreferredSize; it is custom on ALL controls - c.fcommitResize = func(a *allocation, d *sizing) { - C.moveWindow(c.hwnd, C.int(a.x), C.int(a.y), C.int(a.width), C.int(a.height)) - } - c.fgetAuxResizeInfo = func(d *sizing) { - // do nothing - } return c } +func basesetParent(c *controlbase, p *controlParent) { + C.controlSetParent(c.hwnd, p.hwnd) + c.parent = p.hwnd +} + +func basecontainerShow(c *controlbase) { + C.ShowWindow(c.hwnd, C.SW_SHOW) +} + +func basecontainerHide(c *controlbase) { + C.ShowWindow(c.hwnd, C.SW_HIDE) +} + +// don't specify basepreferredSize; it is custom on ALL controls + +func basecommitResize(c *controlbase, a *allocation, d *sizing) { + C.moveWindow(c.hwnd, C.int(a.x), C.int(a.y), C.int(a.width), C.int(a.height)) +} + +func basegetAuxResizeInfo(d *sizing) { + // do nothing +} + // these are provided for convenience func (c *controlbase) text() string { diff --git a/redo/label_windows.go b/redo/label_windows.go index e6b3e7f..516a14c 100644 --- a/redo/label_windows.go +++ b/redo/label_windows.go @@ -7,8 +7,7 @@ import "C" type label struct { *controlbase - standalone bool - supercommitResize func(c *allocation, d *sizing) + standalone bool } var labelclass = toUTF16("STATIC") @@ -25,9 +24,6 @@ func finishNewLabel(text string, standalone bool) *label { controlbase: c, standalone: standalone, } - l.fpreferredSize = l.labelpreferredSize - l.supercommitResize = l.fcommitResize - l.fcommitResize = l.labelcommitResize return l } @@ -47,6 +43,22 @@ func (l *label) SetText(text string) { l.setText(text) } +func (l *label) setParent(p *controlParent) { + basesetParent(l.controlbase, p) +} + +func (l *label) containerShow() { + basecontainerShow(l.controlbase) +} + +func (l *label) containerHide() { + basecontainerHide(l.controlbase) +} + +func (l *label) allocate(x int, y int, width int, height int, d *sizing) []*allocation { + return baseallocate(l, x, y, width, height, d) +} + const ( // via http://msdn.microsoft.com/en-us/library/windows/desktop/dn742486.aspx#sizingandspacing labelHeight = 8 @@ -54,15 +66,19 @@ const ( // TODO the label is offset slightly by default... ) -func (l *label) labelpreferredSize(d *sizing) (width, height int) { +func (l *label) preferredSize(d *sizing) (width, height int) { return int(l.textlen), fromdlgunitsY(labelHeight, d) } -func (l *label) labelcommitResize(c *allocation, d *sizing) { +func (l *label) commitResize(c *allocation, d *sizing) { if !l.standalone { yoff := fromdlgunitsY(labelYOffset, d) c.y += yoff c.height -= yoff } - l.supercommitResize(c, d) + basecommitResize(l.controlbase, c, d) +} + +func (l *label) getAuxResizeInfo(d *sizing) { + basegetAuxResizeInfo(d) } diff --git a/redo/tab_windows.go b/redo/tab_windows.go index e9d116b..032da7e 100644 --- a/redo/tab_windows.go +++ b/redo/tab_windows.go @@ -19,9 +19,7 @@ TODO type tab struct { *controlbase - tabs []*sizer - supersetParent func(p *controlParent) - superallocate func(x int, y int, width int, height int, d *sizing) []*allocation + tabs []*sizer } func newTab() Tab { @@ -31,23 +29,11 @@ func newTab() Tab { t := &tab{ controlbase: c, } - t.supersetParent = t.fsetParent - t.fsetParent = t.tabsetParent - t.fpreferredSize = t.tabpreferredSize - t.superallocate = t.fallocate - t.fallocate = t.taballocate C.controlSetControlFont(t.hwnd) C.setTabSubclass(t.hwnd, unsafe.Pointer(t)) return t } -func (t *tab) tabsetParent(p *controlParent) { - t.supersetParent(p) - for _, c := range t.tabs { - c.child.setParent(p) - } -} - func (t *tab) Append(name string, control Control) { s := new(sizer) t.tabs = append(t.tabs, s) @@ -74,7 +60,28 @@ func tabChanged(data unsafe.Pointer, new C.LRESULT) { t.tabs[int(new)].child.containerShow() } -func (t *tab) tabpreferredSize(d *sizing) (width, height int) { +func (t *tab) setParent(p *controlParent) { + basesetParent(t.controlbase, p) + for _, c := range t.tabs { + c.child.setParent(p) + } +} + +// TODO actually write this +func (t *tab) containerShow() { + basecontainerShow(t.controlbase) +} + +// TODO actually write this +func (t *tab) containerHide() { + basecontainerHide(t.controlbase) +} + +func (t *tab) allocate(x int, y int, width int, height int, d *sizing) []*allocation { + return baseallocate(t, x, y, width, height, d) +} + +func (t *tab) preferredSize(d *sizing) (width, height int) { // TODO only consider the size of the current tab? for _, s := range t.tabs { w, h := s.child.preferredSize(d) @@ -89,22 +96,25 @@ func (t *tab) tabpreferredSize(d *sizing) (width, height int) { } // a tab control contains other controls; size appropriately -// TODO change this to commitResize() -func (t *tab) taballocate(x int, y int, width int, height int, d *sizing) []*allocation { +func (t *tab) commitResize(c *allocation, d *sizing) { var r C.RECT // figure out what the rect for each child is... - r.left = C.LONG(x) // load structure with the window's rect - r.top = C.LONG(y) - r.right = C.LONG(x + width) - r.bottom = C.LONG(y + height) + r.left = C.LONG(c.x) // load structure with the window's rect + r.top = C.LONG(c.y) + r.right = C.LONG(c.x + c.width) + r.bottom = C.LONG(c.y + c.height) C.tabGetContentRect(t.hwnd, &r) - // and allocate - // don't allocate to just the current tab; allocate to all tabs! + // and resize tabs + // don't resize just the current tab; resize all tabs! for _, s := range t.tabs { // because each widget is actually a child of the Window, the origin is the one we calculated above s.resize(int(r.left), int(r.top), int(r.right - r.left), int(r.bottom - r.top)) } - // and now allocate the tab control itself - return t.superallocate(x, y, width, height, d) + // and now resize the tab control itself + basecommitResize(t.controlbase, c, d) +} + +func (t *tab) getAuxResizeInfo(d *sizing) { + basegetAuxResizeInfo(d) } diff --git a/redo/table_windows.go b/redo/table_windows.go index 7dfbc56..633486e 100644 --- a/redo/table_windows.go +++ b/redo/table_windows.go @@ -30,7 +30,6 @@ func finishNewTable(b *tablebase, ty reflect.Type) Table { for i := 0; i < ty.NumField(); i++ { C.tableAppendColumn(t.hwnd, C.int(i), toUTF16(ty.Field(i).Name)) } - t.fpreferredSize = t.tablepreferredSize return t } @@ -43,16 +42,6 @@ func (t *table) Unlock() { C.tableUpdate(t.hwnd, C.int(reflect.Indirect(reflect.ValueOf(t.data)).Len())) } -const ( - // from C++ Template 05 in http://msdn.microsoft.com/en-us/library/windows/desktop/bb226818%28v=vs.85%29.aspx as this is the best I can do for now... (TODO see if I can reliably get item width/height from text size) - tableWidth = 183 - tableHeight = 50 -) - -func (t *table) tablepreferredSize(d *sizing) (width, height int) { - return fromdlgunitsX(tableWidth, d), fromdlgunitsY(tableHeight, d) -} - //export tableGetCellText func tableGetCellText(data unsafe.Pointer, row C.int, col C.int, str *C.LPWSTR) { t := (*table)(data) @@ -63,3 +52,37 @@ func tableGetCellText(data unsafe.Pointer, row C.int, col C.int, str *C.LPWSTR) s := fmt.Sprintf("%v", datum) *str = toUTF16(s) } + +func (t *table) setParent(p *controlParent) { + basesetParent(t.controlbase, p) +} + +func (t *table) containerShow() { + basecontainerShow(t.controlbase) +} + +func (t *table) containerHide() { + basecontainerHide(t.controlbase) +} + +func (t *table) allocate(x int, y int, width int, height int, d *sizing) []*allocation { + return baseallocate(t, x, y, width, height, d) +} + +const ( + // from C++ Template 05 in http://msdn.microsoft.com/en-us/library/windows/desktop/bb226818%28v=vs.85%29.aspx as this is the best I can do for now... (TODO see if I can reliably get item width/height from text size) + tableWidth = 183 + tableHeight = 50 +) + +func (t *table) preferredSize(d *sizing) (width, height int) { + return fromdlgunitsX(tableWidth, d), fromdlgunitsY(tableHeight, d) +} + +func (t *table) commitResize(a *allocation, d *sizing) { + basecommitResize(t.controlbase, a, d) +} + +func (t *table) getAuxResizeInfo(d *sizing) { + basegetAuxResizeInfo(d) +} diff --git a/redo/textfield_windows.go b/redo/textfield_windows.go index d325a18..a2add7a 100644 --- a/redo/textfield_windows.go +++ b/redo/textfield_windows.go @@ -19,7 +19,6 @@ func startNewTextField(style C.DWORD) *textField { t := &textField{ controlbase: c, } - t.fpreferredSize = t.textfieldpreferredSize return t } @@ -39,12 +38,36 @@ func (t *textField) SetText(text string) { t.setText(text) } +func (t *textField) setParent(p *controlParent) { + basesetParent(t.controlbase, p) +} + +func (t *textField) containerShow() { + basecontainerShow(t.controlbase) +} + +func (t *textField) containerHide() { + basecontainerHide(t.controlbase) +} + +func (t *textField) allocate(x int, y int, width int, height int, d *sizing) []*allocation { + return baseallocate(t, x, y, width, height, d) +} + const ( // from http://msdn.microsoft.com/en-us/library/windows/desktop/dn742486.aspx#sizingandspacing textfieldWidth = 107 // this is actually the shorter progress bar width, but Microsoft only indicates as wide as necessary textfieldHeight = 14 ) -func (t *textField) textfieldpreferredSize(d *sizing) (width, height int) { +func (t *textField) preferredSize(d *sizing) (width, height int) { return fromdlgunitsX(textfieldWidth, d), fromdlgunitsY(textfieldHeight, d) } + +func (t *textField) commitResize(a *allocation, d *sizing) { + basecommitResize(t.controlbase, a, d) +} + +func (t *textField) getAuxResizeInfo(d *sizing) { + basegetAuxResizeInfo(d) +}