From 62048303a34f6cac733798651adb53b640e2114a Mon Sep 17 00:00:00 2001 From: Pietro Gagliardi Date: Sat, 18 Oct 2014 17:03:07 -0400 Subject: [PATCH] Merged new container/sizing stuff. --- area_darwin.go | 40 ++++---------- area_unix.go | 38 +++----------- area_windows.c | 2 +- area_windows.go | 47 ++++------------- basicctrls.go | 18 +++---- button_darwin.go | 36 +++---------- button_unix.go | 28 +--------- button_windows.go | 48 ++++------------- checkbox_darwin.go | 40 +++----------- checkbox_unix.go | 28 +--------- checkbox_windows.go | 52 +++++-------------- common_windows.c | 22 ++++---- container.go | 32 ++---------- container_darwin.go | 48 ++++++++++------- container_darwin.m | 28 +++++++--- container_unix.c | 1 - container_unix.go | 59 +++++++++++---------- container_windows.c | 25 ++++----- container_windows.go | 77 ++++++++------------------- control.go | 36 +++++++++---- control_darwin.go | 62 +++++++++++----------- control_unix.go | 121 ++++++++++++++++++++++--------------------- control_windows.go | 64 ++++++++++++++--------- grid.go | 94 +++++++++++++++++++-------------- group_darwin.go | 48 +++++++++-------- group_unix.go | 45 ++++++++-------- group_windows.go | 94 +++++++++++++++++++-------------- label_darwin.go | 46 +++------------- label_unix.go | 42 +++------------ label_windows.go | 62 ++++++---------------- objc_darwin.h | 1 + simplegrid.go | 102 +++++++++++++++++++++--------------- stack.go | 90 ++++++++++++++++++++------------ tab_darwin.go | 50 +++++++++--------- tab_unix.go | 43 +++++++-------- tab_windows.go | 75 +++++++++++++-------------- table_darwin.go | 39 ++++---------- table_unix.go | 29 +---------- table_windows.go | 57 ++++++++------------ textfield_darwin.go | 39 ++++---------- textfield_unix.go | 32 ++---------- textfield_windows.go | 48 ++++------------- winapi_windows.h | 6 ++- window.go | 5 ++ window_darwin.go | 29 ++++++++++- window_darwin.m | 5 ++ window_unix.go | 32 +++++++++++- window_windows.c | 2 +- window_windows.go | 30 ++++++----- yz_repaint_test.go | 1 + zz_test.go | 57 +++++++++++++------- 51 files changed, 928 insertions(+), 1227 deletions(-) diff --git a/area_darwin.go b/area_darwin.go index 1b2dd92..6288ac9 100644 --- a/area_darwin.go +++ b/area_darwin.go @@ -15,8 +15,7 @@ import "C" type area struct { *areabase - _id C.id - scroller *scroller + *scroller textfield C.id textfielddone *event } @@ -26,11 +25,12 @@ func newArea(ab *areabase) Area { areabase: ab, textfielddone: newEvent(), } - a._id = C.newArea(unsafe.Pointer(a)) - a.scroller = newScroller(a._id, false) // no border on Area + id := C.newArea(unsafe.Pointer(a)) + a.scroller = newScroller(id, false) // no border on Area + a.fpreferredSize = a.xpreferredSize a.SetSize(a.width, a.height) a.textfield = C.newTextField() - C.areaSetTextField(a._id, a.textfield) + C.areaSetTextField(a.id, a.textfield) return a } @@ -38,7 +38,7 @@ func (a *area) SetSize(width, height int) { a.width = width a.height = height // set the frame size to set the area's effective size on the Cocoa side - C.moveControl(a._id, 0, 0, C.intptr_t(a.width), C.intptr_t(a.height)) + C.moveControl(a.id, 0, 0, C.intptr_t(a.width), C.intptr_t(a.height)) } func (a *area) Repaint(r image.Rectangle) { @@ -52,18 +52,18 @@ func (a *area) Repaint(r image.Rectangle) { s.y = C.intptr_t(r.Min.Y) s.width = C.intptr_t(r.Dx()) s.height = C.intptr_t(r.Dy()) - C.areaRepaint(a._id, s) + C.areaRepaint(a.id, s) } func (a *area) RepaintAll() { - C.areaRepaintAll(a._id) + C.areaRepaintAll(a.id) } func (a *area) OpenTextFieldAt(x, y int) { if x < 0 || x >= a.width || y < 0 || y >= a.height { panic(fmt.Errorf("point (%d,%d) outside Area in Area.OpenTextFieldAt()", x, y)) } - C.areaTextFieldOpen(a._id, a.textfield, C.intptr_t(x), C.intptr_t(y)) + C.areaTextFieldOpen(a.id, a.textfield, C.intptr_t(x), C.intptr_t(y)) } func (a *area) TextFieldText() string { @@ -238,27 +238,7 @@ func areaView_flagsChanged(self C.id, e C.id, data unsafe.Pointer) C.BOOL { return sendKeyEvent(self, ke, data) } -func (a *area) id() C.id { - return a._id -} - -func (a *area) setParent(p *controlParent) { - a.scroller.setParent(p) -} - -func (a *area) allocate(x int, y int, width int, height int, d *sizing) []*allocation { - return baseallocate(a, x, y, width, height, d) -} - -func (a *area) preferredSize(d *sizing) (width, height int) { +func (a *area) xpreferredSize(d *sizing) (width, height int) { // the preferred size of an Area is its size return a.width, a.height } - -func (a *area) commitResize(c *allocation, d *sizing) { - a.scroller.commitResize(c, d) -} - -func (a *area) getAuxResizeInfo(d *sizing) { - basegetAuxResizeInfo(a, d) -} diff --git a/area_unix.go b/area_unix.go index 2e36628..e434d50 100644 --- a/area_unix.go +++ b/area_unix.go @@ -34,9 +34,8 @@ import "C" type area struct { *areabase - _widget *C.GtkWidget + *scroller drawingarea *C.GtkDrawingArea - scroller *scroller clickCounter *clickCounter @@ -59,7 +58,6 @@ func newArea(ab *areabase) Area { textfieldw := C.gtk_entry_new() a := &area{ areabase: ab, - _widget: widget, drawingarea: (*C.GtkDrawingArea)(unsafe.Pointer(widget)), scroller: newScroller(widget, false, false, true), // not natively scrollable; no border; have an overlay for OpenTextFieldAt() clickCounter: new(clickCounter), @@ -67,6 +65,7 @@ func newArea(ab *areabase) Area { textfield: (*C.GtkEntry)(unsafe.Pointer(textfieldw)), textfielddone: newEvent(), } + a.fpreferredSize = a.xpreferredSize for _, c := range areaCallbacks { g_signal_connect( C.gpointer(unsafe.Pointer(a.drawingarea)), @@ -75,9 +74,9 @@ func newArea(ab *areabase) Area { C.gpointer(unsafe.Pointer(a))) } a.SetSize(a.width, a.height) - C.gtk_overlay_add_overlay(a.scroller.overlay, a.textfieldw) + C.gtk_overlay_add_overlay(a.scroller.overlayoverlay, a.textfieldw) g_signal_connect( - C.gpointer(unsafe.Pointer(a.scroller.overlay)), + C.gpointer(unsafe.Pointer(a.scroller.overlayoverlay)), "get-child-position", area_get_child_position_callback, C.gpointer(unsafe.Pointer(a))) @@ -103,7 +102,7 @@ func newArea(ab *areabase) Area { func (a *area) SetSize(width, height int) { a.width = width a.height = height - C.gtk_widget_set_size_request(a._widget, C.gint(a.width), C.gint(a.height)) + C.gtk_widget_set_size_request(a.widget, C.gint(a.width), C.gint(a.height)) } func (a *area) Repaint(r image.Rectangle) { @@ -111,11 +110,11 @@ func (a *area) Repaint(r image.Rectangle) { if r.Empty() { return } - C.gtk_widget_queue_draw_area(a._widget, C.gint(r.Min.X), C.gint(r.Min.Y), C.gint(r.Dx()), C.gint(r.Dy())) + C.gtk_widget_queue_draw_area(a.widget, C.gint(r.Min.X), C.gint(r.Min.Y), C.gint(r.Dx()), C.gint(r.Dy())) } func (a *area) RepaintAll() { - C.gtk_widget_queue_draw(a._widget) + C.gtk_widget_queue_draw(a.widget) } func (a *area) OpenTextFieldAt(x, y int) { @@ -492,28 +491,7 @@ var modonlykeys = map[C.guint]Modifiers{ C.GDK_KEY_Super_R: Super, } -func (a *area) widget() *C.GtkWidget { - return a._widget -} - -func (a *area) setParent(p *controlParent) { - a.scroller.setParent(p) -} - -func (a *area) allocate(x int, y int, width int, height int, d *sizing) []*allocation { - return baseallocate(a, x, y, width, height, d) -} - -func (a *area) preferredSize(d *sizing) (width, height int) { +func (a *area) xpreferredSize(d *sizing) (width, height int) { // the preferred size of an Area is its size return a.width, a.height } - -func (a *area) commitResize(c *allocation, d *sizing) { - a.scroller.commitResize(c, d) -} - -func (a *area) getAuxResizeInfo(d *sizing) { - // a Label to the left of an Area should be vertically aligned to the top - d.shouldVAlignTop = true -} diff --git a/area_windows.c b/area_windows.c index 115997b..31f705c 100644 --- a/area_windows.c +++ b/area_windows.c @@ -321,7 +321,7 @@ static LRESULT CALLBACK areaWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM uintptr_t heldButtons = (uintptr_t) wParam; LRESULT lResult; - data = getWindowData(hwnd, uMsg, wParam, lParam, &lResult, storeAreaHWND); + data = getWindowData(hwnd, uMsg, wParam, lParam, &lResult); if (data == NULL) return lResult; switch (uMsg) { diff --git a/area_windows.go b/area_windows.go index d95beb1..bf4b9e9 100644 --- a/area_windows.go +++ b/area_windows.go @@ -15,7 +15,7 @@ import "C" type area struct { *areabase - _hwnd C.HWND + *controlSingleHWND clickCounter *clickCounter @@ -39,9 +39,10 @@ func newArea(ab *areabase) Area { clickCounter: new(clickCounter), textfielddone: newEvent(), } - a._hwnd = C.newArea(unsafe.Pointer(a)) + a.controlSingleHWND = newControlSingleHWND(C.newArea(unsafe.Pointer(a))) + a.fpreferredSize = a.xpreferredSize a.SetSize(a.width, a.height) - a.textfield = C.newAreaTextField(a._hwnd, unsafe.Pointer(a)) + a.textfield = C.newAreaTextField(a.hwnd, unsafe.Pointer(a)) C.controlSetControlFont(a.textfield) return a } @@ -49,14 +50,14 @@ func newArea(ab *areabase) Area { func (a *area) SetSize(width, height int) { a.width = width a.height = height - C.SendMessageW(a._hwnd, C.msgAreaSizeChanged, 0, 0) + C.SendMessageW(a.hwnd, C.msgAreaSizeChanged, 0, 0) } func (a *area) Repaint(r image.Rectangle) { var hscroll, vscroll C.int var rect C.RECT - C.SendMessageW(a._hwnd, C.msgAreaGetScroll, C.WPARAM(uintptr(unsafe.Pointer(&hscroll))), C.LPARAM(uintptr(unsafe.Pointer(&vscroll)))) + C.SendMessageW(a.hwnd, C.msgAreaGetScroll, C.WPARAM(uintptr(unsafe.Pointer(&hscroll))), C.LPARAM(uintptr(unsafe.Pointer(&vscroll)))) r = r.Add(image.Pt(int(hscroll), int(vscroll))) // adjust by scroll position r = image.Rect(0, 0, a.width, a.height).Intersect(r) if r.Empty() { @@ -66,18 +67,18 @@ func (a *area) Repaint(r image.Rectangle) { rect.top = C.LONG(r.Min.Y) rect.right = C.LONG(r.Max.X) rect.bottom = C.LONG(r.Max.Y) - C.SendMessageW(a._hwnd, C.msgAreaRepaint, 0, C.LPARAM(uintptr(unsafe.Pointer(&rect)))) + C.SendMessageW(a.hwnd, C.msgAreaRepaint, 0, C.LPARAM(uintptr(unsafe.Pointer(&rect)))) } func (a *area) RepaintAll() { - C.SendMessageW(a._hwnd, C.msgAreaRepaintAll, 0, 0) + C.SendMessageW(a.hwnd, C.msgAreaRepaintAll, 0, 0) } func (a *area) OpenTextFieldAt(x, y int) { if x < 0 || x >= a.width || y < 0 || y >= a.height { panic(fmt.Errorf("point (%d,%d) outside Area in Area.OpenTextFieldAt()", x, y)) } - C.areaOpenTextField(a._hwnd, a.textfield, C.int(x), C.int(y), textfieldWidth, textfieldHeight) + C.areaOpenTextField(a.hwnd, a.textfield, C.int(x), C.int(y), textfieldWidth, textfieldHeight) } func (a *area) TextFieldText() string { @@ -96,7 +97,7 @@ func (a *area) OnTextFieldDismissed(f func()) { //export areaTextFieldDone func areaTextFieldDone(data unsafe.Pointer) { a := (*area)(data) - C.areaMarkTextFieldDone(a._hwnd) + C.areaMarkTextFieldDone(a.hwnd) a.textfielddone.fire() } @@ -323,39 +324,13 @@ var modonlykeys = map[C.WPARAM]Modifiers{ C.VK_RWIN: Super, } -//export storeAreaHWND -func storeAreaHWND(data unsafe.Pointer, hwnd C.HWND) { - a := (*area)(data) - a._hwnd = hwnd -} - //export areaResetClickCounter func areaResetClickCounter(data unsafe.Pointer) { a := (*area)(data) a.clickCounter.reset() } -func (a *area) hwnd() C.HWND { - return a._hwnd -} - -func (a *area) setParent(p *controlParent) { - basesetParent(a, p) -} - -func (a *area) allocate(x int, y int, width int, height int, d *sizing) []*allocation { - return baseallocate(a, x, y, width, height, d) -} - -func (a *area) preferredSize(d *sizing) (width, height int) { +func (a *area) xpreferredSize(d *sizing) (width, height int) { // the preferred size of an Area is its size return a.width, a.height } - -func (a *area) commitResize(c *allocation, d *sizing) { - basecommitResize(a, c, d) -} - -func (a *area) getAuxResizeInfo(d *sizing) { - basegetAuxResizeInfo(a, d) -} diff --git a/basicctrls.go b/basicctrls.go index cee60f2..1ef720b 100644 --- a/basicctrls.go +++ b/basicctrls.go @@ -87,31 +87,20 @@ func NewTab() Tab { // Label is a Control that shows a static line of text. // Label shows one line of text; any text that does not fit is truncated. -// A Label can either have smart vertical alignment relative to the control to its right or just be vertically aligned to the top (standalone). -// The effect of placing a non-standalone Label in any context other than to the immediate left of a Control is undefined. -// Both types of labels are left-aligned. [FUTURE PLANS: For platform-specific horizontal alignment rules, use a Form.] +// Labels are left-aligned. [FUTURE PLANS: For platform-specific horizontal alignment rules, use a Form.] type Label interface { Control // Text and SetText get and set the Label's text. Text() string SetText(text string) - - isStandalone() bool } // NewLabel creates a new Label with the given text. -// The Label will smartly vertically position itself relative to the control to its immediate right. func NewLabel(text string) Label { return newLabel(text) } -// NewStandaloneLabel creates a new Label with the given text. -// The Label will be vertically positioned at the top of its allocated space. -func NewStandaloneLabel(text string) Label { - return newStandaloneLabel(text) -} - // Group is a Control that holds a single Control; if that Control also contains other Controls, then the Controls will appear visually grouped together. // The appearance of a Group varies from system to system; for the most part a Group consists of a thin frame. // All Groups have a text label indicating what the Group is for. @@ -121,6 +110,11 @@ type Group interface { // Text and SetText get and set the Group's label text. Text() string SetText(text string) + + // Margined and SetMargined get and set whether the contents of the Group have a margin around them. + // The size of the margin is platform-dependent. + Margined() bool + SetMargined(margined bool) } // NewGroup creates a new Group with the given text label and child Control. diff --git a/button_darwin.go b/button_darwin.go index 06fac20..1a0cd1b 100644 --- a/button_darwin.go +++ b/button_darwin.go @@ -10,7 +10,7 @@ import ( import "C" type button struct { - _id C.id + *controlSingleObject clicked *event } @@ -18,11 +18,11 @@ func newButton(text string) *button { ctext := C.CString(text) defer C.free(unsafe.Pointer(ctext)) b := &button{ - _id: C.newButton(), + controlSingleObject: newControlSingleObject(C.newButton()), clicked: newEvent(), } - C.buttonSetText(b._id, ctext) - C.buttonSetDelegate(b._id, unsafe.Pointer(b)) + C.buttonSetText(b.id, ctext) + C.buttonSetDelegate(b.id, unsafe.Pointer(b)) return b } @@ -31,13 +31,13 @@ func (b *button) OnClicked(e func()) { } func (b *button) Text() string { - return C.GoString(C.buttonText(b._id)) + return C.GoString(C.buttonText(b.id)) } func (b *button) SetText(text string) { ctext := C.CString(text) defer C.free(unsafe.Pointer(ctext)) - C.buttonSetText(b._id, ctext) + C.buttonSetText(b.id, ctext) } //export buttonClicked @@ -45,27 +45,3 @@ func buttonClicked(xb unsafe.Pointer) { b := (*button)(unsafe.Pointer(xb)) b.clicked.fire() } - -func (b *button) id() C.id { - return b._id -} - -func (b *button) setParent(p *controlParent) { - basesetParent(b, p) -} - -func (b *button) allocate(x int, y int, width int, height int, d *sizing) []*allocation { - return baseallocate(b, x, y, width, height, d) -} - -func (b *button) preferredSize(d *sizing) (width, height int) { - return basepreferredSize(b, d) -} - -func (b *button) commitResize(a *allocation, d *sizing) { - basecommitResize(b, a, d) -} - -func (b *button) getAuxResizeInfo(d *sizing) { - basegetAuxResizeInfo(b, d) -} diff --git a/button_unix.go b/button_unix.go index a880b8c..a1da527 100644 --- a/button_unix.go +++ b/button_unix.go @@ -13,7 +13,7 @@ import ( import "C" type button struct { - _widget *C.GtkWidget + *controlSingleWidget button *C.GtkButton clicked *event } @@ -24,7 +24,7 @@ func newButton(text string) *button { defer freegstr(ctext) widget := C.gtk_button_new_with_label(ctext) b := &button{ - _widget: widget, + controlSingleWidget: newControlSingleWidget(widget), button: (*C.GtkButton)(unsafe.Pointer(widget)), clicked: newEvent(), } @@ -55,27 +55,3 @@ func buttonClicked(bwid *C.GtkButton, data C.gpointer) { b := (*button)(unsafe.Pointer(data)) b.clicked.fire() } - -func (b *button) widget() *C.GtkWidget { - return b._widget -} - -func (b *button) setParent(p *controlParent) { - basesetParent(b, p) -} - -func (b *button) allocate(x int, y int, width int, height int, d *sizing) []*allocation { - return baseallocate(b, x, y, width, height, d) -} - -func (b *button) preferredSize(d *sizing) (width, height int) { - return basepreferredSize(b, d) -} - -func (b *button) commitResize(a *allocation, d *sizing) { - basecommitResize(b, a, d) -} - -func (b *button) getAuxResizeInfo(d *sizing) { - basegetAuxResizeInfo(b, d) -} diff --git a/button_windows.go b/button_windows.go index 38e43d5..ff5a251 100644 --- a/button_windows.go +++ b/button_windows.go @@ -10,8 +10,7 @@ import ( import "C" type button struct { - _hwnd C.HWND - _textlen C.LONG + *controlSingleHWNDWithText clicked *event } @@ -22,12 +21,13 @@ func newButton(text string) *button { C.BS_PUSHBUTTON|C.WS_TABSTOP, 0) b := &button{ - _hwnd: hwnd, + controlSingleHWNDWithText: newControlSingleHWNDWithText(hwnd), clicked: newEvent(), } + b.fpreferredSize = b.xpreferredSize b.SetText(text) - C.controlSetControlFont(b._hwnd) - C.setButtonSubclass(b._hwnd, unsafe.Pointer(b)) + C.controlSetControlFont(b.hwnd) + C.setButtonSubclass(b.hwnd, unsafe.Pointer(b)) return b } @@ -36,11 +36,11 @@ func (b *button) OnClicked(e func()) { } func (b *button) Text() string { - return baseText(b) + return b.text() } func (b *button) SetText(text string) { - baseSetText(b, text) + b.setText(text) } //export buttonClicked @@ -49,51 +49,23 @@ func buttonClicked(data unsafe.Pointer) { b.clicked.fire() } -func (b *button) hwnd() C.HWND { - return b._hwnd -} - -func (b *button) textlen() C.LONG { - return b._textlen -} - -func (b *button) settextlen(len C.LONG) { - b._textlen = len -} - -func (b *button) setParent(p *controlParent) { - basesetParent(b, p) -} - -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) preferredSize(d *sizing) (width, height int) { +func (b *button) xpreferredSize(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 size.cy = 0 - if C.SendMessageW(b._hwnd, C.BCM_GETIDEALSIZE, 0, C.LPARAM(uintptr(unsafe.Pointer(&size)))) != C.FALSE { + if C.SendMessageW(b.hwnd, C.BCM_GETIDEALSIZE, 0, C.LPARAM(uintptr(unsafe.Pointer(&size)))) != C.FALSE { return int(size.cx), int(size.cy) } // that failed, fall back println("message failed; falling back") // don't worry about the error return from GetSystemMetrics(); there's no way to tell (explicitly documented as such) 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, a, d) -} - -func (b *button) getAuxResizeInfo(d *sizing) { - basegetAuxResizeInfo(b, d) + return xmargins + int(b.textlen), fromdlgunitsY(buttonHeight, d) } diff --git a/checkbox_darwin.go b/checkbox_darwin.go index 126662c..1578add 100644 --- a/checkbox_darwin.go +++ b/checkbox_darwin.go @@ -10,7 +10,7 @@ import ( import "C" type checkbox struct { - _id C.id + *controlSingleObject toggled *event } @@ -18,11 +18,11 @@ func newCheckbox(text string) *checkbox { ctext := C.CString(text) defer C.free(unsafe.Pointer(ctext)) c := &checkbox{ - _id: C.newCheckbox(), + controlSingleObject: newControlSingleObject(C.newCheckbox()), toggled: newEvent(), } - C.buttonSetText(c._id, ctext) - C.checkboxSetDelegate(c._id, unsafe.Pointer(c)) + C.buttonSetText(c.id, ctext) + C.checkboxSetDelegate(c.id, unsafe.Pointer(c)) return c } @@ -31,21 +31,21 @@ func (c *checkbox) OnToggled(e func()) { } func (c *checkbox) Text() string { - return C.GoString(C.buttonText(c._id)) + return C.GoString(C.buttonText(c.id)) } func (c *checkbox) SetText(text string) { ctext := C.CString(text) defer C.free(unsafe.Pointer(ctext)) - C.buttonSetText(c._id, ctext) + C.buttonSetText(c.id, ctext) } func (c *checkbox) Checked() bool { - return fromBOOL(C.checkboxChecked(c._id)) + return fromBOOL(C.checkboxChecked(c.id)) } func (c *checkbox) SetChecked(checked bool) { - C.checkboxSetChecked(c._id, toBOOL(checked)) + C.checkboxSetChecked(c.id, toBOOL(checked)) } //export checkboxToggled @@ -53,27 +53,3 @@ func checkboxToggled(xc unsafe.Pointer) { c := (*checkbox)(unsafe.Pointer(xc)) c.toggled.fire() } - -func (c *checkbox) id() C.id { - return c._id -} - -func (c *checkbox) setParent(p *controlParent) { - basesetParent(c, p) -} - -func (c *checkbox) allocate(x int, y int, width int, height int, d *sizing) []*allocation { - return baseallocate(c, x, y, width, height, d) -} - -func (c *checkbox) preferredSize(d *sizing) (width, height int) { - return basepreferredSize(c, d) -} - -func (c *checkbox) commitResize(a *allocation, d *sizing) { - basecommitResize(c, a, d) -} - -func (c *checkbox) getAuxResizeInfo(d *sizing) { - basegetAuxResizeInfo(c, d) -} diff --git a/checkbox_unix.go b/checkbox_unix.go index 7d872a5..fc27bee 100644 --- a/checkbox_unix.go +++ b/checkbox_unix.go @@ -13,7 +13,7 @@ import ( import "C" type checkbox struct { - _widget *C.GtkWidget + *controlSingleWidget button *C.GtkButton toggle *C.GtkToggleButton checkbox *C.GtkCheckButton @@ -25,7 +25,7 @@ func newCheckbox(text string) *checkbox { defer freegstr(ctext) widget := C.gtk_check_button_new_with_label(ctext) c := &checkbox{ - _widget: widget, + controlSingleWidget: newControlSingleWidget(widget), button: (*C.GtkButton)(unsafe.Pointer(widget)), toggle: (*C.GtkToggleButton)(unsafe.Pointer(widget)), checkbox: (*C.GtkCheckButton)(unsafe.Pointer(widget)), @@ -66,27 +66,3 @@ func checkboxToggled(bwid *C.GtkToggleButton, data C.gpointer) { c := (*checkbox)(unsafe.Pointer(data)) c.toggled.fire() } - -func (c *checkbox) widget() *C.GtkWidget { - return c._widget -} - -func (c *checkbox) setParent(p *controlParent) { - basesetParent(c, p) -} - -func (c *checkbox) allocate(x int, y int, width int, height int, d *sizing) []*allocation { - return baseallocate(c, x, y, width, height, d) -} - -func (c *checkbox) preferredSize(d *sizing) (width, height int) { - return basepreferredSize(c, d) -} - -func (c *checkbox) commitResize(a *allocation, d *sizing) { - basecommitResize(c, a, d) -} - -func (c *checkbox) getAuxResizeInfo(d *sizing) { - basegetAuxResizeInfo(c, d) -} diff --git a/checkbox_windows.go b/checkbox_windows.go index 7f8ce85..4ccd5e2 100644 --- a/checkbox_windows.go +++ b/checkbox_windows.go @@ -10,8 +10,7 @@ import ( import "C" type checkbox struct { - _hwnd C.HWND - _textlen C.LONG + *controlSingleHWNDWithText toggled *event } @@ -22,12 +21,13 @@ func newCheckbox(text string) *checkbox { C.BS_CHECKBOX|C.WS_TABSTOP, 0) c := &checkbox{ - _hwnd: hwnd, + controlSingleHWNDWithText: newControlSingleHWNDWithText(hwnd), toggled: newEvent(), } + c.fpreferredSize = c.xpreferredSize c.SetText(text) - C.controlSetControlFont(c._hwnd) - C.setCheckboxSubclass(c._hwnd, unsafe.Pointer(c)) + C.controlSetControlFont(c.hwnd) + C.setCheckboxSubclass(c.hwnd, unsafe.Pointer(c)) return c } @@ -36,23 +36,23 @@ func (c *checkbox) OnToggled(e func()) { } func (c *checkbox) Text() string { - return baseText(c) + return c.text() } func (c *checkbox) SetText(text string) { - baseSetText(c, text) + c.setText(text) } func (c *checkbox) Checked() bool { - return C.checkboxChecked(c._hwnd) != C.FALSE + return C.checkboxChecked(c.hwnd) != C.FALSE } func (c *checkbox) SetChecked(checked bool) { if checked { - C.checkboxSetChecked(c._hwnd, C.TRUE) + C.checkboxSetChecked(c.hwnd, C.TRUE) return } - C.checkboxSetChecked(c._hwnd, C.FALSE) + C.checkboxSetChecked(c.hwnd, C.FALSE) } //export checkboxToggled @@ -61,26 +61,6 @@ func checkboxToggled(data unsafe.Pointer) { c.toggled.fire() } -func (c *checkbox) hwnd() C.HWND { - return c._hwnd -} - -func (c *checkbox) textlen() C.LONG { - return c._textlen -} - -func (c *checkbox) settextlen(len C.LONG) { - c._textlen = len -} - -func (c *checkbox) setParent(p *controlParent) { - basesetParent(c, p) -} - -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 @@ -88,15 +68,7 @@ const ( checkboxXFromLeftOfBoxToLeftOfLabel = 12 ) -func (c *checkbox) preferredSize(d *sizing) (width, height int) { - return fromdlgunitsX(checkboxXFromLeftOfBoxToLeftOfLabel, d) + int(c._textlen), +func (c *checkbox) xpreferredSize(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, a, d) -} - -func (c *checkbox) getAuxResizeInfo(d *sizing) { - basegetAuxResizeInfo(c, d) -} diff --git a/common_windows.c b/common_windows.c index 327e707..d4db842 100644 --- a/common_windows.c +++ b/common_windows.c @@ -29,7 +29,7 @@ void updateWindow(HWND hwnd) xpanic("error calling UpdateWindow()", GetLastError()); } -void *getWindowData(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam, LRESULT *lResult, void (*storeHWND)(void *, HWND)) +void *getWindowData(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam, LRESULT *lResult) { CREATESTRUCTW *cs = (CREATESTRUCTW *) lParam; void *data; @@ -37,11 +37,8 @@ void *getWindowData(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam, LRESULT data = (void *) GetWindowLongPtrW(hwnd, GWLP_USERDATA); if (data == NULL) { // the lpParam is available during WM_NCCREATE and WM_CREATE - if (uMsg == WM_NCCREATE) { + if (uMsg == WM_NCCREATE) SetWindowLongPtrW(hwnd, GWLP_USERDATA, (LONG_PTR) (cs->lpCreateParams)); - data = (void *) GetWindowLongPtrW(hwnd, GWLP_USERDATA); - (*storeHWND)(data, hwnd); - } // act as if we're not ready yet, even during WM_NCCREATE (nothing important to the switch statement below happens here anyway) *lResult = DefWindowProcW(hwnd, uMsg, wParam, lParam); } @@ -106,21 +103,20 @@ void paintControlBackground(HWND hwnd, HDC dc) WCHAR classname[128] = L""; // more than enough to avoid collisions parent = hwnd; - do { - parent = GetParent(parent); - if (parent == NULL) - xpanic("error getting parent container of control in paintControlBackground()", GetLastError()); - // wine sends these messages early, yay... - if (parent == msgwin) - return; + for (;;) { parent = GetParent(parent); if (parent == NULL) xpanic("error getting parent control of control in paintControlBackground()", GetLastError()); + // wine sends these messages early, yay... if (parent == msgwin) return; if (GetClassNameW(parent, classname, 128) == 0) xpanic("error getting name of focused window class in paintControlBackground()", GetLastError()); - } while (_wcsicmp(classname, L"button") == 0); // skip groupboxes + // skip container and groupboxes + if (_wcsicmp(classname, containerclass) != 0) // container + if (_wcsicmp(classname, L"button") != 0) // groupbox + break; + } if (GetWindowRect(hwnd, &r) == 0) xpanic("error getting control's window rect in paintControlBackground()", GetLastError()); // the above is a window rect; convert to client rect diff --git a/container.go b/container.go index 143985c..27674d0 100644 --- a/container.go +++ b/container.go @@ -2,41 +2,14 @@ package ui -type allocation struct { - x int - y int - width int - height int - this Control - neighbor Control -} - type sizingbase struct { - xmargin int - ymargintop int - ymarginbottom int xpadding int ypadding int } -type controlSizing interface { - allocate(x int, y int, width int, height int, d *sizing) []*allocation - preferredSize(*sizing) (int, int) - commitResize(*allocation, *sizing) - getAuxResizeInfo(*sizing) -} - -// A container hosts a Control and resizes that Control based on changes in size to the parent Window. -// container is used by Window, Tab, and Group to contain and control their respective Controls. -// Tab and Group use containers for their content; as such, their commitResize() functions should only change the size of the Tab and Group themselves, and have their containers do the real work. -// All containers must embed containerbase. -type containerbase struct { - child Control -} - -// set to true to apply spacing to all windows -var spaced bool = false +// The container type, which is defined per-platform, is an internal Control that is only used to house other Controls from the underlying UI toolkit's point of view +/* TODO func (c *container) resize(x, y, width, height int) { if c.child == nil { // no children; nothing to do return @@ -50,3 +23,4 @@ func (c *container) resize(x, y, width, height int) { allocations[i].this.commitResize(allocations[i], d) } } +*/ diff --git a/container_darwin.go b/container_darwin.go index b2086c4..58b7777 100644 --- a/container_darwin.go +++ b/container_darwin.go @@ -10,33 +10,44 @@ import ( import "C" type container struct { - containerbase - id C.id + *controlSingleObject } type sizing struct { sizingbase // for size calculations - // nothing for mac + // nothing on Mac OS X // for the actual resizing neighborAlign C.struct_xalignment } -func newContainer(child Control) *container { +func newContainer() *container { c := new(container) - c.id = C.newContainerView(unsafe.Pointer(c)) - c.child = child - c.child.setParent(&controlParent{c.id}) + c.controlSingleObject = newControlSingleObject(C.newContainerView(unsafe.Pointer(c))) return c } -//export containerResized -func containerResized(data unsafe.Pointer, width C.intptr_t, height C.intptr_t) { - c := (*container)(unsafe.Pointer(data)) - // the origin of a view's content area is always (0, 0) - c.resize(0, 0, int(width), int(height)) +func (c *container) parent() *controlParent { + return &controlParent{c.id} +} + +func (c *container) allocation(margined bool) C.struct_xrect { + b := C.containerBounds(c.id) + if margined { + b.x += C.intptr_t(macXMargin) + b.y += C.intptr_t(macYMargin) + b.width -= C.intptr_t(macXMargin) * 2 + b.height -= C.intptr_t(macYMargin) * 2 + } + return b +} + +// we can just return these values as is +func (c *container) bounds(d *sizing) (int, int, int, int) { + b := C.containerBounds(c.id) + return int(b.x), int(b.y), int(b.width), int(b.height) } // These are based on measurements from Interface Builder. @@ -47,18 +58,14 @@ const ( macYPadding = 8 ) -func (c *container) beginResize() (d *sizing) { +func (w *window) beginResize() (d *sizing) { d = new(sizing) - if spaced { - d.xmargin = macXMargin - d.ymargintop = macYMargin - d.ymarginbottom = d.ymargintop - d.xpadding = macXPadding - d.ypadding = macYPadding - } + d.xpadding = macXPadding + d.ypadding = macYPadding return d } +/*TODO func (c *container) translateAllocationCoords(allocations []*allocation, winwidth, winheight int) { for _, a := range allocations { // winheight - y because (0,0) is the bottom-left corner of the window and not the top-left corner @@ -66,3 +73,4 @@ func (c *container) translateAllocationCoords(allocations []*allocation, winwidt a.y = (winheight - a.y) - a.height } } +*/ diff --git a/container_darwin.m b/container_darwin.m index 0a598c6..1dfa7cb 100644 --- a/container_darwin.m +++ b/container_darwin.m @@ -21,12 +21,6 @@ @implementation goContainerView -- (void)setFrameSize:(NSSize)s -{ - [super setFrameSize:s]; - containerResized(self->gocontainer, (intptr_t) s.width, (intptr_t) s.height); -} - @end id newContainerView(void *gocontainer) @@ -40,5 +34,25 @@ id newContainerView(void *gocontainer) void moveControl(id c, intptr_t x, intptr_t y, intptr_t width, intptr_t height) { - [toNSView(c) setFrame:NSMakeRect((CGFloat) x, (CGFloat) y, (CGFloat) width, (CGFloat) height)]; + NSView *v; + NSRect frame; + + frame = NSMakeRect((CGFloat) x, (CGFloat) y, (CGFloat) width, (CGFloat) height); + // mac os x coordinate system has (0,0) in the lower-left + v = toNSView(c); + frame.origin.y = ([[v superview] bounds].size.height - frame.size.height) - frame.origin.y; + [v setFrame:frame]; +} + +struct xrect containerBounds(id view) +{ + NSRect b; + struct xrect r; + + b = [toNSView(view) bounds]; + r.x = (intptr_t) b.origin.x; + r.y = (intptr_t) b.origin.y; + r.width = (intptr_t) b.size.width; + r.height = (intptr_t) b.size.height; + return r; } diff --git a/container_unix.c b/container_unix.c index 9802383..8de3cb5 100644 --- a/container_unix.c +++ b/container_unix.c @@ -59,7 +59,6 @@ static void goContainer_remove(GtkContainer *container, GtkWidget *widget) static void goContainer_size_allocate(GtkWidget *widget, GtkAllocation *allocation) { gtk_widget_set_allocation(widget, allocation); - containerResizing(GOCONTAINER(widget)->gocontainer, allocation); } struct forall { diff --git a/container_unix.go b/container_unix.go index ffb1ffa..90bc84b 100644 --- a/container_unix.go +++ b/container_unix.go @@ -12,9 +12,8 @@ import ( import "C" type container struct { - containerbase - layoutwidget *C.GtkWidget - layoutcontainer *C.GtkContainer + *controlSingleWidget + container *C.GtkContainer } type sizing struct { @@ -24,27 +23,40 @@ type sizing struct { // gtk+ needs nothing // for the actual resizing - shouldVAlignTop bool + // gtk+ needs nothing } -func newContainer(child Control) *container { +func newContainer() *container { c := new(container) - widget := C.newContainer(unsafe.Pointer(c)) - c.layoutwidget = widget - c.layoutcontainer = (*C.GtkContainer)(unsafe.Pointer(widget)) - c.child = child - c.child.setParent(&controlParent{c.layoutcontainer}) + c.controlSingleWidget = newControlSingleWidget(C.newContainer(unsafe.Pointer(c))) + c.container = (*C.GtkContainer)(unsafe.Pointer(c.widget)) return c } -func (c *container) setParent(p *controlParent) { - C.gtk_container_add(p.c, c.layoutwidget) +func (c *container) parent() *controlParent { + return &controlParent{c.container} } -//export containerResizing -func containerResizing(data unsafe.Pointer, r *C.GtkAllocation) { - c := (*container)(data) - c.resize(int(r.x), int(r.y), int(r.width), int(r.height)) +func (c *container) allocation(margined bool) C.GtkAllocation { + var a C.GtkAllocation + + C.gtk_widget_get_allocation(c.widget, &a) + if margined { + a.x += C.int(gtkXMargin) + a.y += C.int(gtkYMargin) + a.width -= C.int(gtkXMargin) * 2 + a.height -= C.int(gtkYMargin) * 2 + } + return a +} + +// we can just return these values as is +// note that allocations of a widget on GTK+ have their origin in the /window/ origin +func (c *container) bounds(d *sizing) (int, int, int, int) { + var a C.GtkAllocation + + C.gtk_widget_get_allocation(c.widget, &a) + return int(a.x), int(a.y), int(a.width), int(a.height) } const ( @@ -54,18 +66,9 @@ const ( gtkYPadding = 6 ) -func (c *container) beginResize() (d *sizing) { +func (w *window) beginResize() (d *sizing) { d = new(sizing) - if spaced { - d.xmargin = gtkXMargin - d.ymargintop = gtkYMargin - d.ymarginbottom = d.ymargintop - d.xpadding = gtkXPadding - d.ypadding = gtkYPadding - } + d.xpadding = gtkXPadding + d.ypadding = gtkYPadding return d } - -func (c *container) translateAllocationCoords(allocations []*allocation, winwidth, winheight int) { - // no need for coordinate conversion with gtk+ -} diff --git a/container_windows.c b/container_windows.c index a95818d..7910b92 100644 --- a/container_windows.c +++ b/container_windows.c @@ -9,25 +9,13 @@ In this case, I chose to waste a window handle rather than keep things super com If this is seriously an issue in the future, I can roll it back. */ -#define containerclass L"gouicontainer" - static LRESULT CALLBACK containerWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { - void *data; - RECT r; LRESULT lResult; - data = getWindowData(hwnd, uMsg, wParam, lParam, &lResult, storeContainerHWND); - if (data == NULL) - return lResult; if (sharedWndProc(hwnd, uMsg, wParam, lParam, &lResult)) return lResult; switch (uMsg) { - case WM_SIZE: - if (GetClientRect(hwnd, &r) == 0) - xpanic("error getting client rect for Window in WM_SIZE", GetLastError()); - containerResize(data, &r); - return 0; default: return DefWindowProcW(hwnd, uMsg, wParam, lParam); } @@ -53,7 +41,7 @@ DWORD makeContainerWindowClass(char **errmsg) return 0; } -HWND newContainer(void *data) +HWND newContainer(void) { HWND hwnd; @@ -63,12 +51,21 @@ HWND newContainer(void *data) WS_CHILD | WS_VISIBLE, CW_USEDEFAULT, CW_USEDEFAULT, 100, 100, - msgwin, NULL, hInstance, data); + msgwin, NULL, hInstance, NULL); if (hwnd == NULL) xpanic("container creation failed", GetLastError()); return hwnd; } +RECT containerBounds(HWND hwnd) +{ + RECT r; + + if (GetClientRect(hwnd, &r) == 0) + xpanic("error getting container client rect for container.bounds()", GetLastError()); + return r; +} + void calculateBaseUnits(HWND hwnd, int *baseX, int *baseY, LONG *internalLeading) { HDC dc; diff --git a/container_windows.go b/container_windows.go index 99be1c2..31963e0 100644 --- a/container_windows.go +++ b/container_windows.go @@ -5,17 +5,13 @@ package ui import ( "fmt" "syscall" - "unsafe" ) // #include "winapi_windows.h" import "C" type container struct { - containerbase - hwnd C.HWND - nchildren int - isGroup bool + *controlSingleHWND } type sizing struct { @@ -40,45 +36,30 @@ func makeContainerWindowClass() error { return nil } -func newContainer(control Control) *container { - c := new(container) - hwnd := C.newContainer(unsafe.Pointer(c)) - if hwnd != c.hwnd { - panic(fmt.Errorf("inconsistency: hwnd returned by CreateWindowEx() (%p) and hwnd stored in container (%p) differ", hwnd, c.hwnd)) +func newContainer() *container { + // don't set preferredSize(); it should never be called + return &container{ + controlSingleHWND: newControlSingleHWND(C.newContainer()), } - c.child = control - c.child.setParent(&controlParent{c}) - return c -} - -func (c *container) setParent(hwnd C.HWND) { - C.controlSetParent(c.hwnd, hwnd) -} - -// this is needed because Windows won't move/resize a child window for us -func (c *container) move(r *C.RECT) { - C.moveWindow(c.hwnd, C.int(r.left), C.int(r.top), C.int(r.right-r.left), C.int(r.bottom-r.top)) } +// TODO merge with controlSingleHWND func (c *container) show() { C.ShowWindow(c.hwnd, C.SW_SHOW) } +// TODO merge with controlSingleHWND func (c *container) hide() { C.ShowWindow(c.hwnd, C.SW_HIDE) } -//export storeContainerHWND -func storeContainerHWND(data unsafe.Pointer, hwnd C.HWND) { - c := (*container)(data) - c.hwnd = hwnd +func (c *container) parent() *controlParent { + return &controlParent{c.hwnd} } -//export containerResize -func containerResize(data unsafe.Pointer, r *C.RECT) { - c := (*container)(data) - // the origin of any window's content area is always (0, 0), but let's use the values from the RECT just to be safe - c.resize(int(r.left), int(r.top), int(r.right-r.left), int(r.bottom-r.top)) +func (c *container) bounds(d *sizing) (int, int, int, int) { + r := C.containerBounds(c.hwnd) + return int(r.left), int(r.top), int(r.right - r.left), int(r.bottom - r.top) } // For Windows, Microsoft just hands you a list of preferred control sizes as part of the MSDN documentation and tells you to roll with it. @@ -103,45 +84,31 @@ func fromdlgunitsY(du int, d *sizing) int { } const ( + // shared by multiple containers marginDialogUnits = 7 paddingDialogUnits = 4 - - groupXMargin = 6 - groupYMarginTop = 11 // note this value /includes the groupbox label/ - groupYMarginBottom = 7 ) -func (c *container) beginResize() (d *sizing) { +func (w *window) beginResize() (d *sizing) { var baseX, baseY C.int var internalLeading C.LONG d = new(sizing) - C.calculateBaseUnits(c.hwnd, &baseX, &baseY, &internalLeading) + C.calculateBaseUnits(w.hwnd, &baseX, &baseY, &internalLeading) d.baseX = baseX d.baseY = baseY d.internalLeading = internalLeading - if spaced { - d.xmargin = fromdlgunitsX(marginDialogUnits, d) - d.ymargintop = fromdlgunitsY(marginDialogUnits, d) - d.ymarginbottom = d.ymargintop - d.xpadding = fromdlgunitsX(paddingDialogUnits, d) - d.ypadding = fromdlgunitsY(paddingDialogUnits, d) - } - if c.isGroup { - // note that these values apply regardless of whether or not spaced is set - // this is because Windows groupboxes have the client rect spanning the entire size of the control, not just the active work area - // the measurements Microsoft give us are for spaced margining; let's just use them - d.xmargin = fromdlgunitsX(groupXMargin, d) - d.ymargintop = fromdlgunitsY(groupYMarginTop, d) - d.ymarginbottom = fromdlgunitsY(groupYMarginBottom, d) - - } + d.xpadding = fromdlgunitsX(paddingDialogUnits, d) + d.ypadding = fromdlgunitsY(paddingDialogUnits, d) return d } -func (c *container) translateAllocationCoords(allocations []*allocation, winwidth, winheight int) { - // no translation needed on windows +func marginRectDLU(r *C.RECT, top int, bottom int, left int, right int, d *sizing) { + r.left += C.LONG(fromdlgunitsX(left, d)) + r.top += C.LONG(fromdlgunitsY(top, d)) + r.right -= C.LONG(fromdlgunitsX(right, d)) + r.bottom -= C.LONG(fromdlgunitsY(bottom, d)) } diff --git a/control.go b/control.go index a8c3ecd..34e5c84 100644 --- a/control.go +++ b/control.go @@ -5,16 +5,32 @@ package ui // Control represents a control. type Control interface { setParent(p *controlParent) // controlParent defined per-platform - controlSizing + preferredSize(d *sizing) (width, height int) + resize(x int, y int, width int, height int, d *sizing) + nTabStops() int // used by the Windows backend } -// 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, - }} +type controlbase struct { + fsetParent func(p *controlParent) + fpreferredSize func(d *sizing) (width, height int) + fresize func(x int, y int, width int, height int, d *sizing) + fnTabStops func() int +} + +// children should not use the same name as these, otherwise weird things will happen + +func (c *controlbase) setParent(p *controlParent) { + c.fsetParent(p) +} + +func (c *controlbase) preferredSize(d *sizing) (width, height int) { + return c.fpreferredSize(d) +} + +func (c *controlbase) resize(x int, y int, width int, height int, d *sizing) { + c.fresize(x, y, width, height, d) +} + +func (c *controlbase) nTabStops() int { + return c.fnTabStops() } diff --git a/control_darwin.go b/control_darwin.go index cc77ed2..330d50c 100644 --- a/control_darwin.go +++ b/control_darwin.go @@ -5,54 +5,52 @@ package ui // #include "objc_darwin.h" import "C" -// all Controls that call base methods must be this -type controlPrivate interface { - id() C.id - Control -} - type controlParent struct { id C.id } -func basesetParent(c controlPrivate, p *controlParent) { - // redrawing the new window handled by C.parent() - C.parent(c.id(), p.id) +type controlSingleObject struct { + *controlbase + id C.id } -func basepreferredSize(c controlPrivate, d *sizing) (int, int) { - s := C.controlPreferredSize(c.id()) +func newControlSingleObject(id C.id) *controlSingleObject { + c := new(controlSingleObject) + c.controlbase = &controlbase{ + fsetParent: c.xsetParent, + fpreferredSize: c.xpreferredSize, + fresize: c.xresize, + } + c.id = id + return c +} + +func (c *controlSingleObject) xsetParent(p *controlParent) { + // redrawing the new window handled by C.parent() + C.parent(c.id, p.id) +} + +func (c *controlSingleObject) xpreferredSize(d *sizing) (int, int) { + s := C.controlPreferredSize(c.id) return int(s.width), int(s.height) } -func basecommitResize(c controlPrivate, a *allocation, d *sizing) { - dobasecommitResize(c.id(), a, d) -} - -func dobasecommitResize(id C.id, c *allocation, d *sizing) { - C.moveControl(id, C.intptr_t(c.x), C.intptr_t(c.y), C.intptr_t(c.width), C.intptr_t(c.height)) -} - -func basegetAuxResizeInfo(c controlPrivate, d *sizing) { - d.neighborAlign = C.alignmentInfoFrame(c.id()) +func (c *controlSingleObject) xresize(x int, y int, width int, height int, d *sizing) { + C.moveControl(c.id, C.intptr_t(x), C.intptr_t(y), C.intptr_t(width), C.intptr_t(height)) } type scroller struct { - id C.id + *controlSingleObject + scroller *controlSingleObject } func newScroller(child C.id, bordered bool) *scroller { - id := C.newScrollView(child, toBOOL(bordered)) + sid := C.newScrollView(child, toBOOL(bordered)) s := &scroller{ - id: id, + controlSingleObject: newControlSingleObject(child), + scroller: newControlSingleObject(sid), } + s.fsetParent = s.scroller.fsetParent + s.fresize = s .scroller.fresize return s } - -func (s *scroller) setParent(p *controlParent) { - C.parent(s.id, p.id) -} - -func (s *scroller) commitResize(c *allocation, d *sizing) { - dobasecommitResize(s.id, c, d) -} diff --git a/control_unix.go b/control_unix.go index e8200c4..6aa2278 100644 --- a/control_unix.go +++ b/control_unix.go @@ -11,24 +11,34 @@ import ( // #include "gtk_unix.h" import "C" -// all Controls that call base methods must be this -type controlPrivate interface { - widget() *C.GtkWidget - Control -} - type controlParent struct { c *C.GtkContainer } -func basesetParent(c controlPrivate, p *controlParent) { - widget := c.widget() // avoid multiple interface lookups - C.gtk_container_add(p.c, widget) - // make sure the new widget is shown if not explicitly hidden - C.gtk_widget_show_all(widget) +type controlSingleWidget struct { + *controlbase + widget *C.GtkWidget } -func basepreferredSize(c controlPrivate, d *sizing) (int, int) { +func newControlSingleWidget(widget *C.GtkWidget) *controlSingleWidget { + c := new(controlSingleWidget) + c.controlbase = &controlbase{ + fsetParent: c.xsetParent, + fpreferredSize: c.xpreferredSize, + fresize: c.xresize, + } + c.widget = widget + return c +} + +func (c *controlSingleWidget) xsetParent(p *controlParent) { + C.gtk_container_add(p.c, c.widget) + // make sure the new widget is shown if not explicitly hidden + // TODO why did I have this again? + C.gtk_widget_show_all(c.widget) +} + +func (c *controlSingleWidget) xpreferredSize(d *sizing) (int, int) { // GTK+ 3 makes this easy: controls can tell us what their preferred size is! // ...actually, it tells us two things: the "minimum size" and the "natural size". // The "minimum size" is the smallest size we /can/ display /anything/. The "natural size" is the smallest size we would /prefer/ to display. @@ -37,15 +47,11 @@ func basepreferredSize(c controlPrivate, d *sizing) (int, int) { // There is a warning about height-for-width controls, but in my tests this isn't an issue. var r C.GtkRequisition - C.gtk_widget_get_preferred_size(c.widget(), nil, &r) + C.gtk_widget_get_preferred_size(c.widget, nil, &r) return int(r.width), int(r.height) } -func basecommitResize(c controlPrivate, a *allocation, d *sizing) { - dobasecommitResize(c.widget(), a, d) -} - -func dobasecommitResize(w *C.GtkWidget, c *allocation, d *sizing) { +func (c *controlSingleWidget) xresize(x int, y int, width int, height int, d *sizing) { // as we resize on size-allocate, we have to also use size-allocate on our children // this is fine anyway; in fact, this allows us to move without knowing what the container is! // this is what GtkBox does anyway @@ -53,68 +59,63 @@ func dobasecommitResize(w *C.GtkWidget, c *allocation, d *sizing) { var r C.GtkAllocation - r.x = C.int(c.x) - r.y = C.int(c.y) - r.width = C.int(c.width) - r.height = C.int(c.height) - C.gtk_widget_size_allocate(w, &r) -} - -func basegetAuxResizeInfo(c Control, d *sizing) { - // controls set this to true if a Label to its left should be vertically aligned to the control's top - d.shouldVAlignTop = false + r.x = C.int(x) + r.y = C.int(y) + r.width = C.int(width) + r.height = C.int(height) + C.gtk_widget_size_allocate(c.widget, &r) } type scroller struct { + *controlSingleWidget + + scroller *controlSingleWidget scrollwidget *C.GtkWidget scrollcontainer *C.GtkContainer scrollwindow *C.GtkScrolledWindow + overlay *controlSingleWidget overlaywidget *C.GtkWidget overlaycontainer *C.GtkContainer - overlay *C.GtkOverlay - - addShowWhich *C.GtkWidget + overlayoverlay *C.GtkOverlay } func newScroller(widget *C.GtkWidget, native bool, bordered bool, overlay bool) *scroller { - var o *C.GtkWidget + s := new(scroller) + s.controlSingleWidget = newControlSingleWidget(widget) + s.scrollwidget = C.gtk_scrolled_window_new(nil, nil) + s.scrollcontainer = (*C.GtkContainer)(unsafe.Pointer(s.scrollwidget)) + s.scrollwindow = (*C.GtkScrolledWindow)(unsafe.Pointer(s.scrollwidget)) - scrollwidget := C.gtk_scrolled_window_new(nil, nil) - if overlay { - o = C.gtk_overlay_new() - } - s := &scroller{ - scrollwidget: scrollwidget, - scrollcontainer: (*C.GtkContainer)(unsafe.Pointer(scrollwidget)), - scrollwindow: (*C.GtkScrolledWindow)(unsafe.Pointer(scrollwidget)), - overlaywidget: o, - overlaycontainer: (*C.GtkContainer)(unsafe.Pointer(o)), - overlay: (*C.GtkOverlay)(unsafe.Pointer(o)), + // any actual changing operations need to be done to the GtkScrolledWindow + // that is, everything /except/ preferredSize() are done to the GtkScrolledWindow + s.scroller = newControlSingleWidget(s.scrollwidget) + s.fsetParent = s.scroller.fsetParent + s.fresize = s.scroller.fresize + + // in GTK+ 3.4 we still technically need to use the separate gtk_scrolled_window_add_with_viewpoint()/gtk_container_add() spiel for adding the widget to the scrolled window + if native { + C.gtk_container_add(s.scrollcontainer, s.widget) + } else { + C.gtk_scrolled_window_add_with_viewport(s.scrollwindow, s.widget) } + // give the scrolled window a border (thanks to jlindgren in irc.gimp.net/#gtk+) if bordered { C.gtk_scrolled_window_set_shadow_type(s.scrollwindow, C.GTK_SHADOW_IN) } - if native { - C.gtk_container_add(s.scrollcontainer, widget) - } else { - C.gtk_scrolled_window_add_with_viewport(s.scrollwindow, widget) - } - s.addShowWhich = s.scrollwidget + if overlay { + // ok things get REALLY fun now + // we now have to do all of the above again + s.overlaywidget = C.gtk_overlay_new() + s.overlaycontainer = (*C.GtkContainer)(unsafe.Pointer(s.overlaywidget)) + s.overlayoverlay = (*C.GtkOverlay)(unsafe.Pointer(s.overlaywidget)) + s.overlay = newControlSingleWidget(s.overlaywidget) + s.fsetParent = s.overlay.fsetParent + s.fresize = s.overlay.fresize C.gtk_container_add(s.overlaycontainer, s.scrollwidget) - s.addShowWhich = s.overlaywidget } + return s } - -func (s *scroller) setParent(p *controlParent) { - C.gtk_container_add(p.c, s.addShowWhich) - // see basesetParent() above for why we call gtk_widget_show_all() - C.gtk_widget_show_all(s.addShowWhich) -} - -func (s *scroller) commitResize(c *allocation, d *sizing) { - dobasecommitResize(s.addShowWhich, c, d) -} diff --git a/control_windows.go b/control_windows.go index d48fa54..ffe0222 100644 --- a/control_windows.go +++ b/control_windows.go @@ -5,45 +5,59 @@ package ui // #include "winapi_windows.h" import "C" -type controlPrivate interface { - hwnd() C.HWND - Control -} - type controlParent struct { - c *container + hwnd C.HWND } -func basesetParent(c controlPrivate, p *controlParent) { - C.controlSetParent(c.hwnd(), p.c.hwnd) - p.c.nchildren++ +// don't specify preferredSize in any of these; they're per-control + +type controlSingleHWND struct { + *controlbase + hwnd C.HWND } -// don't specify basepreferredSize; it is custom on ALL controls - -func basecommitResize(c controlPrivate, 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 newControlSingleHWND(hwnd C.HWND) *controlSingleHWND { + c := new(controlSingleHWND) + c.controlbase = &controlbase{ + fsetParent: c.xsetParent, + fresize: c.xresize, + fnTabStops: func() int { + // most controls count as one tab stop + return 1 + }, + } + c.hwnd = hwnd + return c } -func basegetAuxResizeInfo(c controlPrivate, d *sizing) { - // do nothing +func (c *controlSingleHWND) xsetParent(p *controlParent) { + C.controlSetParent(c.hwnd, p.hwnd) +} + +func (c *controlSingleHWND) xresize(x int, y int, width int, height int, d *sizing) { + C.moveWindow(c.hwnd, C.int(x), C.int(y), C.int(width), C.int(height)) } // these are provided for convenience -type textableControl interface { - controlPrivate - textlen() C.LONG - settextlen(C.LONG) +type controlSingleHWNDWithText struct { + *controlSingleHWND + textlen C.LONG } -func baseText(c textableControl) string { - return getWindowText(c.hwnd()) +func newControlSingleHWNDWithText(h C.HWND) *controlSingleHWNDWithText { + return &controlSingleHWNDWithText{ + controlSingleHWND: newControlSingleHWND(h), + } } -func baseSetText(c textableControl, text string) { - hwnd := c.hwnd() +// TODO export these instead of requiring dummy declarations in each implementation +func (c *controlSingleHWNDWithText) text() string { + return getWindowText(c.hwnd) +} + +func (c *controlSingleHWNDWithText) setText(text string) { t := toUTF16(text) - C.setWindowText(hwnd, t) - c.settextlen(C.controlTextLength(hwnd, t)) + C.setWindowText(c.hwnd, t) + c.textlen = C.controlTextLength(c.hwnd, t) } diff --git a/grid.go b/grid.go index 6142a4f..9e68e66 100644 --- a/grid.go +++ b/grid.go @@ -27,6 +27,11 @@ type Grid interface { // The effect of overlapping spanning Controls is also undefined. // Add panics if either xspan or yspan are zero or negative. Add(control Control, nextTo Control, side Side, xexpand bool, xalign Align, yexpand bool, yalign Align, xspan int, yspan int) + + // Padded and SetPadded get and set whether the controls of the Grid have padding between them. + // The size of the padding is platform-dependent. + Padded() bool + SetPadded(padded bool) } // Align represents the alignment of a Control in its cell of a Grid. @@ -54,7 +59,8 @@ type grid struct { controls []gridCell indexof map[Control]int prev int - parent *controlParent + container *container + padded bool xmax int ymax int @@ -84,6 +90,7 @@ type gridCell struct { func NewGrid() Grid { return &grid{ indexof: map[Control]int{}, + container: newContainer(), } } @@ -129,9 +136,7 @@ func (g *grid) Add(control Control, nextTo Control, side Side, xexpand bool, xal xspan: xspan, yspan: yspan, } - if g.parent != nil { - control.setParent(g.parent) - } + control.setParent(g.container.parent()) // if this is the first control, just add it in directly if len(g.controls) != 0 { next := g.prev @@ -161,11 +166,16 @@ func (g *grid) Add(control Control, nextTo Control, side Side, xexpand bool, xal g.reorigin() } +func (g *grid) Padded() bool { + return g.padded +} + +func (g *grid) SetPadded(padded bool) { + g.padded = padded +} + func (g *grid) setParent(p *controlParent) { - g.parent = p - for i := range g.controls { - g.controls[i].control.setParent(g.parent) - } + g.container.setParent(p) } // builds the topological cell grid; also makes colwidths and rowheights @@ -187,15 +197,27 @@ func (g *grid) mkgrid() (gg [][]int, colwidths []int, rowheights []int) { return gg, make([]int, g.xmax), make([]int, g.ymax) } -func (g *grid) allocate(x int, y int, width int, height int, d *sizing) (allocations []*allocation) { +func (g *grid) resize(x int, y int, width int, height int, d *sizing) { + g.container.resize(x, y, width, height, d) + if len(g.controls) == 0 { // nothing to do - return nil + return + } + + x, y, width, height = g.container.bounds(d) + + // -2) get this Grid's padding + xpadding := d.xpadding + ypadding := d.ypadding + if !g.padded { + xpadding = 0 + ypadding = 0 } // -1) discount padding from width/height - width -= (g.xmax - 1) * d.xpadding - height -= (g.ymax - 1) * d.ypadding + width -= (g.xmax - 1) * xpadding + height -= (g.ymax - 1) * ypadding // 0) build necessary data structures gg, colwidths, rowheights := g.mkgrid() @@ -317,11 +339,11 @@ func (g *grid) allocate(x int, y int, width int, height int, d *sizing) (allocat if i != prev { g.controls[i].finalx = curx } else { - g.controls[i].finalwidth += d.xpadding + g.controls[i].finalwidth += xpadding } g.controls[i].finalwidth += colwidths[x] } - curx += colwidths[x] + d.xpadding + curx += colwidths[x] + xpadding prev = i } } @@ -334,11 +356,11 @@ func (g *grid) allocate(x int, y int, width int, height int, d *sizing) (allocat if i != prev { g.controls[i].finaly = cury } else { - g.controls[i].finalheight += d.ypadding + g.controls[i].finalheight += ypadding } g.controls[i].finalheight += rowheights[y] } - cury += rowheights[y] + d.ypadding + cury += rowheights[y] + ypadding prev = i } } @@ -367,29 +389,17 @@ func (g *grid) allocate(x int, y int, width int, height int, d *sizing) (allocat } // 8) and FINALLY we draw - var current *allocation - for _, ycol := range gg { - current = nil for _, i := range ycol { if i != -1 { // treat empty cells like spaces - as := g.controls[i].control.allocate( + g.controls[i].control.resize( g.controls[i].finalx+x, g.controls[i].finaly+y, g.controls[i].finalwidth, g.controls[i].finalheight, d) - if current != nil { // connect first left to first right - current.neighbor = g.controls[i].control - } - if len(as) != 0 { - current = as[0] // next left is first subwidget - } else { - current = nil // spaces don't have allocation data - } - allocations = append(allocations, as...) } } } - return allocations + return } func (g *grid) preferredSize(d *sizing) (width, height int) { @@ -398,6 +408,14 @@ func (g *grid) preferredSize(d *sizing) (width, height int) { return 0, 0 } + // -1) get this Grid's padding + xpadding := d.xpadding + ypadding := d.ypadding + if !g.padded { + xpadding = 0 + ypadding = 0 + } + // 0) build necessary data structures gg, colwidths, rowheights := g.mkgrid() @@ -434,14 +452,14 @@ func (g *grid) preferredSize(d *sizing) (width, height int) { } // and that's it; just account for padding - return colwidth + (g.xmax-1)*d.xpadding, - rowheight + (g.ymax-1)*d.ypadding + return colwidth + (g.xmax-1) * xpadding, + rowheight + (g.ymax-1) * ypadding } -func (g *grid) commitResize(a *allocation, d *sizing) { - // do nothing; needed to satisfy Control -} - -func (g *grid) getAuxResizeInfo(d *sizing) { - // do nothing; needed to satisfy Control +func (g *grid) nTabStops() int { + n := 0 + for _, c := range g.controls { + n += c.control.nTabStops() + } + return n } diff --git a/group_darwin.go b/group_darwin.go index ad3610a..23dd563 100644 --- a/group_darwin.go +++ b/group_darwin.go @@ -10,49 +10,51 @@ import ( import "C" type group struct { - _id C.id + *controlSingleObject - *container + child Control + container *container + + margined bool + + chainresize func(x int, y int, width int, height int, d *sizing) } func newGroup(text string, control Control) Group { g := new(group) - g.container = newContainer(control) - g._id = C.newGroup(g.container.id) + g.container = newContainer() + g.controlSingleObject = newControlSingleObject(C.newGroup(g.container.id)) + g.child = control + g.child.setParent(g.container.parent()) g.SetText(text) + g.chainresize = g.fresize + g.fresize = g.xresize return g } func (g *group) Text() string { - return C.GoString(C.groupText(g._id)) + return C.GoString(C.groupText(g.id)) } func (g *group) SetText(text string) { ctext := C.CString(text) defer C.free(unsafe.Pointer(ctext)) - C.groupSetText(g._id, ctext) + C.groupSetText(g.id, ctext) } -func (g *group) id() C.id { - return g._id +func (g *group) Margined() bool { + return g.margined } -func (g *group) setParent(p *controlParent) { - basesetParent(g, p) +func (g *group) SetMargined(margined bool) { + g.margined = margined } -func (g *group) allocate(x int, y int, width int, height int, d *sizing) []*allocation { - return baseallocate(g, x, y, width, height, d) -} +func (g *group) xresize(x int, y int, width int, height int, d *sizing) { + // first, chain up to change the GtkFrame and its child container + g.chainresize(x, y, width, height, d) -func (g *group) preferredSize(d *sizing) (width, height int) { - return basepreferredSize(g, d) -} - -func (g *group) commitResize(a *allocation, d *sizing) { - basecommitResize(g, a, d) -} - -func (g *group) getAuxResizeInfo(d *sizing) { - basegetAuxResizeInfo(g, d) + // now that the container has the correct size, we can resize the child + a := g.container.allocation(g.margined) + g.child.resize(int(a.x), int(a.y), int(a.width), int(a.height), d) } diff --git a/group_unix.go b/group_unix.go index b59b158..681ec75 100644 --- a/group_unix.go +++ b/group_unix.go @@ -12,11 +12,16 @@ import ( import "C" type group struct { - _widget *C.GtkWidget + *controlSingleWidget gcontainer *C.GtkContainer frame *C.GtkFrame - *container + child Control + container *container + + margined bool + + chainresize func(x int, y int, width int, height int, d *sizing) } func newGroup(text string, control Control) Group { @@ -24,9 +29,10 @@ func newGroup(text string, control Control) Group { defer freegstr(ctext) widget := C.gtk_frame_new(ctext) g := &group{ - _widget: widget, + controlSingleWidget: newControlSingleWidget(widget), gcontainer: (*C.GtkContainer)(unsafe.Pointer(widget)), frame: (*C.GtkFrame)(unsafe.Pointer(widget)), + child: control, } // with GTK+, groupboxes by default have frames and slightly x-offset regular text @@ -46,9 +52,13 @@ func newGroup(text string, control Control) Group { C.gtk_label_set_attributes(label, boldlist) C.pango_attr_list_unref(boldlist) // thanks baedert in irc.gimp.net/#gtk+ - g.container = newContainer(control) + g.container = newContainer() + g.child.setParent(g.container.parent()) g.container.setParent(&controlParent{g.gcontainer}) + g.chainresize = g.fresize + g.fresize = g.xresize + return g } @@ -62,26 +72,19 @@ func (g *group) SetText(text string) { C.gtk_frame_set_label(g.frame, ctext) } -func (g *group) widget() *C.GtkWidget { - return g._widget +func (g *group) Margined() bool { + return g.margined } -func (g *group) setParent(p *controlParent) { - basesetParent(g, p) +func (g *group) SetMargined(margined bool) { + g.margined = margined } -func (g *group) allocate(x int, y int, width int, height int, d *sizing) []*allocation { - return baseallocate(g, x, y, width, height, d) -} +func (g *group) xresize(x int, y int, width int, height int, d *sizing) { + // first, chain up to change the GtkFrame and its child container + g.chainresize(x, y, width, height, d) -func (g *group) preferredSize(d *sizing) (width, height int) { - return basepreferredSize(g, d) -} - -func (g *group) commitResize(a *allocation, d *sizing) { - basecommitResize(g, a, d) -} - -func (g *group) getAuxResizeInfo(d *sizing) { - basegetAuxResizeInfo(g, d) + // now that the container has the correct size, we can resize the child + a := g.container.allocation(g.margined) + g.child.resize(int(a.x), int(a.y), int(a.width), int(a.height), d) } diff --git a/group_windows.go b/group_windows.go index 05956b0..d4acdda 100644 --- a/group_windows.go +++ b/group_windows.go @@ -6,10 +6,10 @@ package ui import "C" type group struct { - _hwnd C.HWND - _textlen C.LONG - - *container + *controlSingleHWNDWithText + child Control + margined bool + chainresize func(x int, y int, width int, height int, d *sizing) } func newGroup(text string, control Control) Group { @@ -17,66 +17,82 @@ func newGroup(text string, control Control) Group { C.BS_GROUPBOX, C.WS_EX_CONTROLPARENT) g := &group{ - _hwnd: hwnd, - container: newContainer(control), + controlSingleHWNDWithText: newControlSingleHWNDWithText(hwnd), + child: control, } + g.fpreferredSize = g.xpreferredSize + g.chainresize = g.fresize + g.fresize = g.xresize + g.fnTabStops = control.nTabStops // groupbox itself is not tabbable but the contents might be g.SetText(text) - C.controlSetControlFont(g._hwnd) - g.container.setParent(g._hwnd) - g.container.isGroup = true + C.controlSetControlFont(g.hwnd) + control.setParent(&controlParent{g.hwnd}) return g } func (g *group) Text() string { - return baseText(g) + return g.text() } func (g *group) SetText(text string) { - baseSetText(g, text) + g.setText(text) } -func (g *group) hwnd() C.HWND { - return g._hwnd +func (g *group) Margined() bool { + return g.margined } -func (g *group) textlen() C.LONG { - return g._textlen +func (g *group) SetMargined(margined bool) { + g.margined = margined } -func (g *group) settextlen(len C.LONG) { - g._textlen = len -} +const ( + groupXMargin = 6 + groupYMarginTop = 11 // note this value /includes the groupbox label/ + groupYMarginBottom = 7 +) -func (g *group) setParent(p *controlParent) { - basesetParent(g, p) -} +func (g *group) xpreferredSize(d *sizing) (width, height int) { + var r C.RECT -func (g *group) allocate(x int, y int, width int, height int, d *sizing) []*allocation { - return baseallocate(g, x, y, width, height, d) -} - -func (g *group) preferredSize(d *sizing) (width, height int) { width, height = g.child.preferredSize(d) - if width < int(g._textlen) { // if the text is longer, try not to truncate - width = int(g._textlen) + if width < int(g.textlen) { // if the text is longer, try not to truncate + width = int(g.textlen) } - // the two margin constants come from container_windows.go - return width, height + fromdlgunitsY(groupYMarginTop, d) + fromdlgunitsY(groupYMarginBottom, d) + r.left = 0 + r.top = 0 + r.right = C.LONG(width) + r.bottom = C.LONG(height) + // use negative numbers to increase the size of the rectangle + if g.margined { + marginRectDLU(&r, -groupYMarginTop, -groupYMarginBottom, -groupXMargin, -groupXMargin, d) + } else { + // unforutnately, as mentioned above, the size of a groupbox includes the label and border + // 1DLU on each side should be enough to make up for that; TODO is not, we can change it + // TODO make these named constants + marginRectDLU(&r, -1, -1, -1, -1, d) + } + return int(r.right - r.left), int(r.bottom - r.top) } -func (g *group) commitResize(c *allocation, d *sizing) { +func (g *group) xresize(x int, y int, width int, height int, d *sizing) { + // first, chain up to the container base to keep the Z-order correct + g.chainresize(x, y, width, height, d) + + // now resize the child container var r C.RECT // pretend that the client area of the group box only includes the actual empty space // container will handle the necessary adjustments properly r.left = 0 r.top = 0 - r.right = C.LONG(c.width) - r.bottom = C.LONG(c.height) - g.container.move(&r) - basecommitResize(g, c, d) -} - -func (g *group) getAuxResizeInfo(d *sizing) { - basegetAuxResizeInfo(g, d) + r.right = C.LONG(width) + r.bottom = C.LONG(height) + if g.margined { + // see above + marginRectDLU(&r, groupYMarginTop, groupYMarginBottom, groupXMargin, groupXMargin, d) + } else { + marginRectDLU(&r, 1, 1, 1, 1, d) + } + g.child.resize(int(r.left), int(r.top), int(r.right - r.left), int(r.bottom - r.top), d) } diff --git a/label_darwin.go b/label_darwin.go index 0ad73e7..64a15b1 100644 --- a/label_darwin.go +++ b/label_darwin.go @@ -10,57 +10,28 @@ import ( import "C" type label struct { - _id C.id - standalone bool + *controlSingleObject } -func finishNewLabel(text string, standalone bool) *label { +func newLabel(text string) Label { l := &label{ - _id: C.newLabel(), - standalone: standalone, + controlSingleObject: newControlSingleObject(C.newLabel()), } l.SetText(text) return l } -func newLabel(text string) Label { - return finishNewLabel(text, false) -} - -func newStandaloneLabel(text string) Label { - return finishNewLabel(text, true) -} - func (l *label) Text() string { - return C.GoString(C.textfieldText(l._id)) + return C.GoString(C.textfieldText(l.id)) } func (l *label) SetText(text string) { ctext := C.CString(text) defer C.free(unsafe.Pointer(ctext)) - C.textfieldSetText(l._id, ctext) -} - -func (l *label) isStandalone() bool { - return l.standalone -} - -func (l *label) id() C.id { - return l._id -} - -func (l *label) setParent(p *controlParent) { - basesetParent(l, p) -} - -func (l *label) allocate(x int, y int, width int, height int, d *sizing) []*allocation { - return baseallocate(l, x, y, width, height, d) -} - -func (l *label) preferredSize(d *sizing) (width, height int) { - return basepreferredSize(l, d) + C.textfieldSetText(l.id, ctext) } +/*TODO func (l *label) commitResize(c *allocation, d *sizing) { if !l.standalone && c.neighbor != nil { c.neighbor.getAuxResizeInfo(d) @@ -89,7 +60,4 @@ func (l *label) commitResize(c *allocation, d *sizing) { } basecommitResize(l, c, d) } - -func (l *label) getAuxResizeInfo(d *sizing) { - basegetAuxResizeInfo(l, d) -} +*/ diff --git a/label_unix.go b/label_unix.go index 750a7f8..819c0e4 100644 --- a/label_unix.go +++ b/label_unix.go @@ -9,40 +9,34 @@ import ( ) // #include "gtk_unix.h" -// extern void buttonClicked(GtkButton *, gpointer); -// extern void checkboxToggled(GtkToggleButton *, gpointer); import "C" type label struct { - _widget *C.GtkWidget + *controlSingleWidget misc *C.GtkMisc label *C.GtkLabel - standalone bool } -func finishNewLabel(text string, standalone bool) *label { +func newLabel(text string) Label { ctext := togstr(text) defer freegstr(ctext) widget := C.gtk_label_new(ctext) l := &label{ - _widget: widget, + controlSingleWidget: newControlSingleWidget(widget), misc: (*C.GtkMisc)(unsafe.Pointer(widget)), label: (*C.GtkLabel)(unsafe.Pointer(widget)), - standalone: standalone, } return l } -func newLabel(text string) Label { - return finishNewLabel(text, false) -} - +/*TODO func newStandaloneLabel(text string) Label { l := finishNewLabel(text, true) // standalone labels are always at the top left C.gtk_misc_set_alignment(l.misc, 0, 0) return l } +*/ func (l *label) Text() string { return fromgstr(C.gtk_label_get_text(l.label)) @@ -54,26 +48,7 @@ func (l *label) SetText(text string) { C.gtk_label_set_text(l.label, ctext) } -func (l *label) isStandalone() bool { - return l.standalone -} - -func (l *label) widget() *C.GtkWidget { - return l._widget -} - -func (l *label) setParent(p *controlParent) { - basesetParent(l, p) -} - -func (l *label) allocate(x int, y int, width int, height int, d *sizing) []*allocation { - return baseallocate(l, x, y, width, height, d) -} - -func (l *label) preferredSize(d *sizing) (width, height int) { - return basepreferredSize(l, d) -} - +/*TODO func (l *label) commitResize(c *allocation, d *sizing) { if !l.standalone && c.neighbor != nil { c.neighbor.getAuxResizeInfo(d) @@ -86,7 +61,4 @@ func (l *label) commitResize(c *allocation, d *sizing) { } basecommitResize(l, c, d) } - -func (l *label) getAuxResizeInfo(d *sizing) { - basegetAuxResizeInfo(l, d) -} +*/ diff --git a/label_windows.go b/label_windows.go index df39e51..8f8f120 100644 --- a/label_windows.go +++ b/label_windows.go @@ -6,67 +6,37 @@ package ui import "C" type label struct { - _hwnd C.HWND - _textlen C.LONG + *controlSingleHWNDWithText standalone bool } var labelclass = toUTF16("STATIC") -func finishNewLabel(text string, standalone bool) *label { +func newLabel(text string) Label { hwnd := C.newControl(labelclass, // SS_NOPREFIX avoids accelerator translation; SS_LEFTNOWORDWRAP clips text past the end // controls are vertically aligned to the top by default (thanks Xeek in irc.freenode.net/#winapi) C.SS_NOPREFIX|C.SS_LEFTNOWORDWRAP, C.WS_EX_TRANSPARENT) l := &label{ - _hwnd: hwnd, - standalone: standalone, + controlSingleHWNDWithText: newControlSingleHWNDWithText(hwnd), + } + l.fpreferredSize = l.xpreferredSize + l.fnTabStops = func() int { + // labels are not tab stops + return 0 } l.SetText(text) - C.controlSetControlFont(l._hwnd) + C.controlSetControlFont(l.hwnd) return l } -func newLabel(text string) Label { - return finishNewLabel(text, false) -} - -func newStandaloneLabel(text string) Label { - return finishNewLabel(text, true) -} - func (l *label) Text() string { - return baseText(l) + return l.text() } func (l *label) SetText(text string) { - baseSetText(l, text) -} - -func (l *label) isStandalone() bool { - return l.standalone -} - -func (l *label) hwnd() C.HWND { - return l._hwnd -} - -func (l *label) textlen() C.LONG { - return l._textlen -} - -func (l *label) settextlen(len C.LONG) { - l._textlen = len -} - -func (l *label) setParent(p *controlParent) { - C.controlSetParent(l.hwnd(), p.c.hwnd) - // don't increment p.c.nchildren here because Labels aren't tab stops -} - -func (l *label) allocate(x int, y int, width int, height int, d *sizing) []*allocation { - return baseallocate(l, x, y, width, height, d) + l.setText(text) } const ( @@ -75,10 +45,11 @@ const ( labelYOffset = 3 ) -func (l *label) preferredSize(d *sizing) (width, height int) { - return int(l._textlen), fromdlgunitsY(labelHeight, d) +func (l *label) xpreferredSize(d *sizing) (width, height int) { + return int(l.textlen), fromdlgunitsY(labelHeight, d) } +/*TODO func (l *label) commitResize(c *allocation, d *sizing) { if !l.standalone { yoff := fromdlgunitsY(labelYOffset, d) @@ -93,7 +64,4 @@ func (l *label) commitResize(c *allocation, d *sizing) { } basecommitResize(l, c, d) } - -func (l *label) getAuxResizeInfo(d *sizing) { - basegetAuxResizeInfo(l, d) -} +*/ diff --git a/objc_darwin.h b/objc_darwin.h index 5fa1557..9f51b8e 100644 --- a/objc_darwin.h +++ b/objc_darwin.h @@ -83,6 +83,7 @@ extern void groupSetText(id, char *); /* container_darwin.m */ extern id newContainerView(void *); extern void moveControl(id, intptr_t, intptr_t, intptr_t, intptr_t); +extern struct xrect containerBounds(id); /* tab_darwin.m */ extern id newTab(void); diff --git a/simplegrid.go b/simplegrid.go index 7e8527f..aac2586 100644 --- a/simplegrid.go +++ b/simplegrid.go @@ -14,8 +14,6 @@ import ( // One Control can be marked as "stretchy": when the Window containing the SimpleGrid is resized, the cell containing that Control resizes to take any remaining space; its row and column are adjusted accordingly (so other filling controls in the same row and column will fill to the new height and width, respectively). // A stretchy Control implicitly fills its cell. // All cooridnates in a SimpleGrid are given in (row,column) form with (0,0) being the top-left cell. -// -// As a special rule, to ensure proper appearance, non-standalone Labels are automatically made filling. type SimpleGrid interface { Control @@ -28,6 +26,11 @@ type SimpleGrid interface { // Only one control can be stretchy per SimpleGrid; calling SetStretchy multiple times merely changes which control is stretchy (preserving the previous filling value). // It panics if the given coordinate is invalid. SetStretchy(row int, column int) + + // Padded and SetPadded get and set whether the controls of the SimpleGrid have padding between them. + // The size of the padding is platform-dependent. + Padded() bool + SetPadded(padded bool) } type simpleGrid struct { @@ -37,6 +40,8 @@ type simpleGrid struct { stretchyfill bool widths, heights [][]int // caches to avoid reallocating each time rowheights, colwidths []int + container *container + padded bool } // NewSimpleGrid creates a new SimpleGrid with the given Controls. @@ -64,13 +69,10 @@ func NewSimpleGrid(nPerRow int, controls ...Control) SimpleGrid { ch[row] = make([]int, nPerRow) for x := 0; x < nPerRow; x++ { cc[row][x] = controls[i] - if l, ok := controls[i].(Label); ok && !l.isStandalone() { - cf[row][x] = true - } i++ } } - return &simpleGrid{ + g := &simpleGrid{ controls: cc, filling: cf, stretchyrow: -1, @@ -79,7 +81,15 @@ func NewSimpleGrid(nPerRow int, controls ...Control) SimpleGrid { heights: ch, rowheights: make([]int, nRows), colwidths: make([]int, nPerRow), + container: newContainer(), } + p := g.container.parent() + for _, cc := range g.controls { + for _, c := range cc { + c.setParent(p) + } + } + return g } func (g *simpleGrid) SetFilling(row int, column int) { @@ -102,15 +112,19 @@ func (g *simpleGrid) SetStretchy(row int, column int) { g.filling[g.stretchyrow][g.stretchycol] = true } -func (g *simpleGrid) setParent(parent *controlParent) { - for _, col := range g.controls { - for _, c := range col { - c.setParent(parent) - } - } +func (g *simpleGrid) Padded() bool { + return g.padded } -func (g *simpleGrid) allocate(x int, y int, width int, height int, d *sizing) (allocations []*allocation) { +func (g *simpleGrid) SetPadded(padded bool) { + g.padded = padded +} + +func (g *simpleGrid) setParent(parent *controlParent) { + g.container.setParent(parent) +} + +func (g *simpleGrid) resize(x int, y int, width int, height int, d *sizing) { max := func(a int, b int) int { if a > b { return a @@ -118,14 +132,21 @@ func (g *simpleGrid) allocate(x int, y int, width int, height int, d *sizing) (a return b } - var current *allocation // for neighboring - + g.container.resize(x, y, width, height, d) if len(g.controls) == 0 { - return nil + return } - // 0) inset the available rect by the needed padding - width -= (len(g.colwidths) - 1) * d.xpadding - height -= (len(g.rowheights) - 1) * d.ypadding + x, y, width, height = g.container.bounds(d) + // -1) get this SimpleGrid's padding + xpadding := d.xpadding + ypadding := d.ypadding + if !g.padded { + xpadding = 0 + ypadding = 0 + } + // 0) inset the available rect by the needed padding and reset x/y for children + width -= (len(g.colwidths) - 1) * xpadding + height -= (len(g.rowheights) - 1) * ypadding // 1) clear data structures for i := range g.rowheights { g.rowheights[i] = 0 @@ -161,7 +182,6 @@ func (g *simpleGrid) allocate(x int, y int, width int, height int, d *sizing) (a // 4) draw startx := x for row, xcol := range g.controls { - current = nil // reset on new columns for col, c := range xcol { w := g.widths[row][col] h := g.heights[row][col] @@ -169,22 +189,12 @@ func (g *simpleGrid) allocate(x int, y int, width int, height int, d *sizing) (a w = g.colwidths[col] h = g.rowheights[row] } - as := c.allocate(x, y, w, h, d) - if current != nil { // connect first left to first right - current.neighbor = c - } - if len(as) != 0 { - current = as[0] // next left is first subwidget - } else { - current = nil // spaces don't have allocation data - } - allocations = append(allocations, as...) - x += g.colwidths[col] + d.xpadding + c.resize(x, y, w, h, d) + x += g.colwidths[col] + xpadding } x = startx - y += g.rowheights[row] + d.ypadding + y += g.rowheights[row] + ypadding } - return } // filling and stretchy are ignored for preferred size calculation @@ -196,8 +206,14 @@ func (g *simpleGrid) preferredSize(d *sizing) (width int, height int) { return b } - width -= (len(g.colwidths) - 1) * d.xpadding - height -= (len(g.rowheights) - 1) * d.ypadding + xpadding := d.xpadding + ypadding := d.ypadding + if !g.padded { + xpadding = 0 + ypadding = 0 + } + width -= (len(g.colwidths) - 1) * xpadding + height -= (len(g.rowheights) - 1) * ypadding // 1) clear data structures for i := range g.rowheights { g.rowheights[i] = 0 @@ -225,10 +241,12 @@ func (g *simpleGrid) preferredSize(d *sizing) (width int, height int) { return width, height } -func (g *simpleGrid) commitResize(c *allocation, d *sizing) { - // this is to satisfy Control; nothing to do here -} - -func (g *simpleGrid) getAuxResizeInfo(d *sizing) { - // this is to satisfy Control; nothing to do here -} +func (g *simpleGrid) nTabStops() int { + n := 0 + for _, cc := range g.controls { + for _, c := range cc { + n += c.nTabStops() + } + } + return n +} \ No newline at end of file diff --git a/stack.go b/stack.go index 12fc08f..f210ec8 100644 --- a/stack.go +++ b/stack.go @@ -24,6 +24,11 @@ type Stack interface { // SetStretchy marks a control in a Stack as stretchy. // It panics if index is out of range. SetStretchy(index int) + + // Padded and SetPadded get and set whether the controls of the Stack have padding between them. + // The size of the padding is platform-dependent. + Padded() bool + SetPadded(padded bool) } type stack struct { @@ -31,16 +36,24 @@ type stack struct { controls []Control stretchy []bool width, height []int // caches to avoid reallocating these each time + container *container + padded bool } func newStack(o orientation, controls ...Control) Stack { - return &stack{ + s := &stack{ orientation: o, controls: controls, stretchy: make([]bool, len(controls)), width: make([]int, len(controls)), height: make([]int, len(controls)), + container: newContainer(), } + p := s.container.parent() + for _, c := range s.controls { + c.setParent(p) + } + return s } // NewHorizontalStack creates a new Stack that arranges the given Controls horizontally. @@ -60,24 +73,38 @@ func (s *stack) SetStretchy(index int) { s.stretchy[index] = true } -func (s *stack) setParent(parent *controlParent) { - for _, c := range s.controls { - c.setParent(parent) - } +func (s *stack) Padded() bool { + return s.padded } -func (s *stack) allocate(x int, y int, width int, height int, d *sizing) (allocations []*allocation) { - var stretchywid, stretchyht int - var current *allocation // for neighboring +func (s *stack) SetPadded(padded bool) { + s.padded = padded +} +func (s *stack) setParent(parent *controlParent) { + s.container.setParent(parent) +} + +func (s *stack) resize(x int, y int, width int, height int, d *sizing) { + var stretchywid, stretchyht int + + s.container.resize(x, y, width, height, d) if len(s.controls) == 0 { // do nothing if there's nothing to do - return nil + return + } + x, y, width, height = s.container.bounds(d) + // -1) get this Stack's padding + xpadding := d.xpadding + ypadding := d.ypadding + if !s.padded { + xpadding = 0 + ypadding = 0 } // 0) inset the available rect by the needed padding if s.orientation == horizontal { - width -= (len(s.controls) - 1) * d.xpadding + width -= (len(s.controls) - 1) * xpadding } else { - height -= (len(s.controls) - 1) * d.ypadding + height -= (len(s.controls) - 1) * ypadding } // 1) get height and width of non-stretchy controls; figure out how much space is alloted to stretchy controls stretchywid = width @@ -116,25 +143,14 @@ func (s *stack) allocate(x int, y int, width int, height int, d *sizing) (alloca } // 3) now actually place controls for i, c := range s.controls { - as := c.allocate(x, y, s.width[i], s.height[i], d) - if s.orientation == horizontal { // no vertical neighbors - if current != nil { // connect first left to first right - current.neighbor = c - } - if len(as) != 0 { - current = as[0] // next left is first subwidget - } else { - current = nil // spaces don't have allocation data - } - } - allocations = append(allocations, as...) + c.resize(x, y, s.width[i], s.height[i], d) if s.orientation == horizontal { - x += s.width[i] + d.xpadding + x += s.width[i] + xpadding } else { - y += s.height[i] + d.ypadding + y += s.height[i] + ypadding } } - return allocations + return } // The preferred size of a Stack is the sum of the preferred sizes of non-stretchy controls + (the number of stretchy controls * the largest preferred size among all stretchy controls). @@ -152,10 +168,16 @@ func (s *stack) preferredSize(d *sizing) (width int, height int) { if len(s.controls) == 0 { // no controls, so return emptiness return 0, 0 } + xpadding := d.xpadding + ypadding := d.ypadding + if !s.padded { + xpadding = 0 + ypadding = 0 + } if s.orientation == horizontal { - width = (len(s.controls) - 1) * d.xpadding + width = (len(s.controls) - 1) * xpadding } else { - height = (len(s.controls) - 1) * d.ypadding + height = (len(s.controls) - 1) * ypadding } for i, c := range s.controls { w, h := c.preferredSize(d) @@ -184,13 +206,15 @@ func (s *stack) preferredSize(d *sizing) (width int, height int) { return } -func (s *stack) commitResize(c *allocation, d *sizing) { - // this is to satisfy Control; nothing to do here +func (s *stack) nTabStops() int { + n := 0 + for _, c := range s.controls { + n += c.nTabStops() + } + return n } -func (s *stack) getAuxResizeInfo(d *sizing) { - // this is to satisfy Control; nothing to do here -} +// TODO the below needs to be changed // Space returns a null Control intended for padding layouts with blank space. // It appears to its owner as a Control of 0x0 size. diff --git a/tab_darwin.go b/tab_darwin.go index b33827d..112b81e 100644 --- a/tab_darwin.go +++ b/tab_darwin.go @@ -10,46 +10,44 @@ import ( import "C" type tab struct { - _id C.id - tabs []*container + *controlSingleObject + tabs []*container + children []Control + chainresize func(x int, y int, width int, height int, d *sizing) } func newTab() Tab { - return &tab{ - _id: C.newTab(), + t := &tab{ + controlSingleObject: newControlSingleObject(C.newTab()), } + t.fpreferredSize = t.xpreferredSize + t.chainresize = t.fresize + t.fresize = t.xresize + return t } func (t *tab) Append(name string, control Control) { - c := newContainer(control) + c := newContainer() t.tabs = append(t.tabs, c) + control.setParent(c.parent()) + t.children = append(t.children, control) cname := C.CString(name) defer C.free(unsafe.Pointer(cname)) - C.tabAppend(t._id, cname, c.id) + C.tabAppend(t.id, cname, c.id) } -func (t *tab) id() C.id { - return t._id -} - -func (t *tab) setParent(p *controlParent) { - basesetParent(t, p) -} - -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) { - s := C.tabPreferredSize(t._id) +func (t *tab) xpreferredSize(d *sizing) (width, height int) { + s := C.tabPreferredSize(t.id) return int(s.width), int(s.height) } -// no need to override Control.commitResize() as only prepared the tabbed control; its children will be resized when that one is resized (and NSTabView itself will call setFrame: for us) -func (t *tab) commitResize(a *allocation, d *sizing) { - basecommitResize(t, a, d) -} +func (t *tab) xresize(x int, y int, width int, height int, d *sizing) { + // first, chain up to change the GtkFrame and its child container + t.chainresize(x, y, width, height, d) -func (t *tab) getAuxResizeInfo(d *sizing) { - basegetAuxResizeInfo(t, d) + // now that the containers have the correct size, we can resize the children + for i, _ := range t.tabs { + a := t.tabs[i].allocation(false/*TODO*/) + t.children[i].resize(int(a.x), int(a.y), int(a.width), int(a.height), d) + } } diff --git a/tab_unix.go b/tab_unix.go index 79f7844..064f17f 100644 --- a/tab_unix.go +++ b/tab_unix.go @@ -12,30 +12,37 @@ import ( import "C" type tab struct { - _widget *C.GtkWidget + *controlSingleWidget container *C.GtkContainer notebook *C.GtkNotebook tabs []*container + children []Control + + chainresize func(x int, y int, width int, height int, d *sizing) } func newTab() Tab { widget := C.gtk_notebook_new() t := &tab{ - _widget: widget, + controlSingleWidget: newControlSingleWidget(widget), container: (*C.GtkContainer)(unsafe.Pointer(widget)), notebook: (*C.GtkNotebook)(unsafe.Pointer(widget)), } + t.chainresize = t.fresize + t.fresize = t.xresize // there are no scrolling arrows by default; add them in case there are too many tabs C.gtk_notebook_set_scrollable(t.notebook, C.TRUE) return t } func (t *tab) Append(name string, control Control) { - c := newContainer(control) + c := newContainer() t.tabs = append(t.tabs, c) // this calls gtk_container_add(), which, according to gregier in irc.gimp.net/#gtk+, acts just like gtk_notebook_append_page() c.setParent(&controlParent{t.container}) + control.setParent(c.parent()) + t.children = append(t.children, control) cname := togstr(name) defer freegstr(cname) C.gtk_notebook_set_tab_label_text(t.notebook, @@ -44,27 +51,13 @@ func (t *tab) Append(name string, control Control) { cname) } -func (t *tab) widget() *C.GtkWidget { - return t._widget -} +func (t *tab) xresize(x int, y int, width int, height int, d *sizing) { + // first, chain up to change the GtkFrame and its child container + t.chainresize(x, y, width, height, d) -func (t *tab) setParent(p *controlParent) { - basesetParent(t, p) -} - -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) { - return basepreferredSize(t, d) -} - -// no need to override Control.commitResize() as only prepared the tabbed control; its children will be reallocated when that one is resized -func (t *tab) commitResize(a *allocation, d *sizing) { - basecommitResize(t, a, d) -} - -func (t *tab) getAuxResizeInfo(d *sizing) { - basegetAuxResizeInfo(t, d) + // now that the containers have the correct size, we can resize the children + for i, _ := range t.tabs { + a := t.tabs[i].allocation(false/*TODO*/) + t.children[i].resize(int(a.x), int(a.y), int(a.width), int(a.height), d) + } } diff --git a/tab_windows.go b/tab_windows.go index 05d75d0..7203166 100644 --- a/tab_windows.go +++ b/tab_windows.go @@ -12,12 +12,14 @@ import "C" /* On Windows, container controls are just regular controls that notify their parent when the user wants to do things; changing the contents of a switching container (such as a tab control) must be done manually. -We'll create a dummy window using the pre-existing Window window class for each tab page. This makes showing and hiding tabs a matter of showing and hiding one control. +We'll create a dummy window using the container window class for each tab page. This makes showing and hiding tabs a matter of showing and hiding one control. */ type tab struct { - _hwnd C.HWND - tabs []*container + *controlSingleHWND + tabs []*container + children []Control + chainresize func(x int, y int, width int, height int, d *sizing) } func newTab() Tab { @@ -25,22 +27,29 @@ func newTab() Tab { C.TCS_TOOLTIPS|C.WS_TABSTOP, 0) // don't set WS_EX_CONTROLPARENT here; see uitask_windows.c t := &tab{ - _hwnd: hwnd, + controlSingleHWND: newControlSingleHWND(hwnd), } - C.controlSetControlFont(t._hwnd) - C.setTabSubclass(t._hwnd, unsafe.Pointer(t)) + t.fpreferredSize = t.xpreferredSize + t.chainresize = t.fresize + t.fresize = t.xresize + // count tabs as 1 tab stop; the actual number of tab stops varies + C.controlSetControlFont(t.hwnd) + C.setTabSubclass(t.hwnd, unsafe.Pointer(t)) return t } +// TODO margined func (t *tab) Append(name string, control Control) { - c := newContainer(control) - c.setParent(t._hwnd) + c := newContainer() + control.setParent(&controlParent{c.hwnd}) + c.setParent(&controlParent{t.hwnd}) t.tabs = append(t.tabs, c) + t.children = append(t.children, control) // initially hide tab 1..n controls; if we don't, they'll appear over other tabs, resulting in weird behavior if len(t.tabs) != 1 { t.tabs[len(t.tabs)-1].hide() } - C.tabAppend(t._hwnd, toUTF16(name)) + C.tabAppend(t.hwnd, toUTF16(name)) } //export tabChanging @@ -61,27 +70,15 @@ func tabTabHasChildren(data unsafe.Pointer, which C.LRESULT) C.BOOL { if len(t.tabs) == 0 { // currently no tabs return C.FALSE } - if t.tabs[int(which)].nchildren > 0 { + if t.children[int(which)].nTabStops() > 0 { return C.TRUE } return C.FALSE } -func (t *tab) hwnd() C.HWND { - return t._hwnd -} - -func (t *tab) setParent(p *controlParent) { - basesetParent(t, p) -} - -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) { - for _, s := range t.tabs { - w, h := s.child.preferredSize(d) +func (t *tab) xpreferredSize(d *sizing) (width, height int) { + for _, c := range t.children { + w, h := c.preferredSize(d) if width < w { width = w } @@ -89,30 +86,30 @@ func (t *tab) preferredSize(d *sizing) (width, height int) { height = h } } - return width, height + int(C.tabGetTabHeight(t._hwnd)) + return width, height + int(C.tabGetTabHeight(t.hwnd)) } // a tab control contains other controls; size appropriately -func (t *tab) commitResize(c *allocation, d *sizing) { +func (t *tab) xresize(x int, y int, width int, height int, d *sizing) { + // first, chain up to the container base to keep the Z-order correct + t.chainresize(x, y, width, height, d) + + // now resize the children var r C.RECT // figure out what the rect for each child is... - // the tab contents are children of the tab itself, so ignore c.x and c.y, which are relative to the window! + // the tab contents are children of the tab itself, so ignore x and y, which are relative to the window! r.left = C.LONG(0) r.top = C.LONG(0) - r.right = C.LONG(c.width) - r.bottom = C.LONG(c.height) - C.tabGetContentRect(t._hwnd, &r) + r.right = C.LONG(width) + r.bottom = C.LONG(height) + C.tabGetContentRect(t.hwnd, &r) // and resize tabs // don't resize just the current tab; resize all tabs! - for _, c := range t.tabs { + for i, _ := range t.tabs { // because each widget is actually a child of the Window, the origin is the one we calculated above - c.move(&r) + t.tabs[i].resize(int(r.left), int(r.top), int(r.right - r.left), int(r.bottom - r.top), d) + // TODO get the actual client rect + t.children[i].resize(int(0), int(0), int(r.right - r.left), int(r.bottom - r.top), d) } - // and now resize the tab control itself - basecommitResize(t, c, d) -} - -func (t *tab) getAuxResizeInfo(d *sizing) { - basegetAuxResizeInfo(t, d) } diff --git a/table_darwin.go b/table_darwin.go index 9caf0fe..60a3614 100644 --- a/table_darwin.go +++ b/table_darwin.go @@ -14,8 +14,7 @@ import "C" type table struct { *tablebase - _id C.id - scroller *scroller + *scroller images []C.id selected *event @@ -24,13 +23,13 @@ type table struct { func finishNewTable(b *tablebase, ty reflect.Type) Table { id := C.newTable() t := &table{ - _id: id, scroller: newScroller(id, true), // border on Table tablebase: b, selected: newEvent(), } + t.fpreferredSize = t.xpreferredSize // also sets the delegate - C.tableMakeDataSource(t._id, unsafe.Pointer(t)) + C.tableMakeDataSource(t.id, unsafe.Pointer(t)) for i := 0; i < ty.NumField(); i++ { cname := C.CString(ty.Field(i).Name) coltype := C.colTypeText @@ -42,7 +41,7 @@ func finishNewTable(b *tablebase, ty reflect.Type) Table { coltype = C.colTypeCheckbox editable = true } - C.tableAppendColumn(t._id, C.intptr_t(i), cname, C.int(coltype), toBOOL(editable)) + C.tableAppendColumn(t.id, C.intptr_t(i), cname, C.int(coltype), toBOOL(editable)) C.free(unsafe.Pointer(cname)) // free now (not deferred) to conserve memory } return t @@ -56,7 +55,7 @@ func (t *table) Unlock() { Do(func() { t.RLock() defer t.RUnlock() - C.tableUpdate(t._id) + C.tableUpdate(t.id) }) }() } @@ -68,13 +67,13 @@ func (t *table) LoadImageList(i ImageList) { func (t *table) Selected() int { t.RLock() defer t.RUnlock() - return int(C.tableSelected(t._id)) + return int(C.tableSelected(t.id)) } func (t *table) Select(index int) { t.RLock() defer t.RUnlock() - C.tableSelect(t._id, C.intptr_t(index)) + C.tableSelect(t.id, C.intptr_t(index)) } func (t *table) OnSelected(f func()) { @@ -132,27 +131,7 @@ func tableSelectionChanged(data unsafe.Pointer) { t.selected.fire() } -func (t *table) id() C.id { - return t._id -} - -func (t *table) setParent(p *controlParent) { - t.scroller.setParent(p) -} - -func (t *table) allocate(x int, y int, width int, height int, d *sizing) []*allocation { - return baseallocate(t, x, y, width, height, d) -} - -func (t *table) preferredSize(d *sizing) (width, height int) { - s := C.tablePreferredSize(t._id) +func (t *table) xpreferredSize(d *sizing) (width, height int) { + s := C.tablePreferredSize(t.id) return int(s.width), int(s.height) } - -func (t *table) commitResize(c *allocation, d *sizing) { - t.scroller.commitResize(c, d) -} - -func (t *table) getAuxResizeInfo(d *sizing) { - basegetAuxResizeInfo(t, d) -} diff --git a/table_unix.go b/table_unix.go index 62fb7e2..931a28e 100644 --- a/table_unix.go +++ b/table_unix.go @@ -18,9 +18,8 @@ import "C" type table struct { *tablebase - _widget *C.GtkWidget + *scroller treeview *C.GtkTreeView - scroller *scroller model *C.goTableModel modelgtk *C.GtkTreeModel @@ -48,7 +47,6 @@ func finishNewTable(b *tablebase, ty reflect.Type) Table { t := &table{ scroller: newScroller(widget, true, true, false), // natively scrollable; has a border; no overlay tablebase: b, - _widget: widget, treeview: (*C.GtkTreeView)(unsafe.Pointer(widget)), crtocol: make(map[*C.GtkCellRendererToggle]int), selected: newEvent(), @@ -222,28 +220,3 @@ func tableSelectionChanged(sel *C.GtkTreeSelection, data C.gpointer) { t := (*table)(unsafe.Pointer(data)) t.selected.fire() } - -func (t *table) widget() *C.GtkWidget { - return t._widget -} - -func (t *table) setParent(p *controlParent) { - t.scroller.setParent(p) -} - -func (t *table) allocate(x int, y int, width int, height int, d *sizing) []*allocation { - return baseallocate(t, x, y, width, height, d) -} - -func (t *table) preferredSize(d *sizing) (width, height int) { - return basepreferredSize(t, d) -} - -func (t *table) commitResize(c *allocation, d *sizing) { - t.scroller.commitResize(c, d) -} - -func (t *table) getAuxResizeInfo(d *sizing) { - // a Label to the left of a Table should be vertically aligned to the top - d.shouldVAlignTop = true -} diff --git a/table_windows.go b/table_windows.go index c606654..ceeb504 100644 --- a/table_windows.go +++ b/table_windows.go @@ -13,7 +13,7 @@ import "C" type table struct { *tablebase - _hwnd C.HWND + *controlSingleHWND noautosize bool colcount C.int hotrow C.int @@ -21,13 +21,15 @@ type table struct { pushedrow C.int pushedcol C.int selected *event + chainresize func(x int, y int, width int, height int, d *sizing) } func finishNewTable(b *tablebase, ty reflect.Type) Table { + hwnd := C.newControl(C.xWC_LISTVIEW, + C.LVS_REPORT|C.LVS_OWNERDATA|C.LVS_NOSORTHEADER|C.LVS_SHOWSELALWAYS|C.LVS_SINGLESEL|C.WS_HSCROLL|C.WS_VSCROLL|C.WS_TABSTOP, + C.WS_EX_CLIENTEDGE) // WS_EX_CLIENTEDGE without WS_BORDER will show the canonical visual styles border (thanks to MindChild in irc.efnet.net/#winprog) t := &table{ - _hwnd: C.newControl(C.xWC_LISTVIEW, - C.LVS_REPORT|C.LVS_OWNERDATA|C.LVS_NOSORTHEADER|C.LVS_SHOWSELALWAYS|C.LVS_SINGLESEL|C.WS_HSCROLL|C.WS_VSCROLL|C.WS_TABSTOP, - C.WS_EX_CLIENTEDGE), // WS_EX_CLIENTEDGE without WS_BORDER will show the canonical visual styles border (thanks to MindChild in irc.efnet.net/#winprog) + controlSingleHWND: newControlSingleHWND(hwnd), tablebase: b, hotrow: -1, hotcol: -1, @@ -35,14 +37,17 @@ func finishNewTable(b *tablebase, ty reflect.Type) Table { pushedcol: -1, selected: newEvent(), } - C.setTableSubclass(t._hwnd, unsafe.Pointer(t)) + t.fpreferredSize = t.xpreferredSize + t.chainresize = t.fresize + t.fresize = t.xresize + C.setTableSubclass(t.hwnd, unsafe.Pointer(t)) // LVS_EX_FULLROWSELECT gives us selection across the whole row, not just the leftmost column; this makes the list view work like on other platforms // LVS_EX_SUBITEMIMAGES gives us images in subitems, which will be important when both images and checkboxes are added - C.tableAddExtendedStyles(t._hwnd, C.LVS_EX_FULLROWSELECT|C.LVS_EX_SUBITEMIMAGES) + C.tableAddExtendedStyles(t.hwnd, C.LVS_EX_FULLROWSELECT|C.LVS_EX_SUBITEMIMAGES) // this must come after the subclass because it uses one of our private messages - C.SendMessageW(t._hwnd, C.msgTableMakeInitialCheckboxImageList, 0, 0) + C.SendMessageW(t.hwnd, C.msgTableMakeInitialCheckboxImageList, 0, 0) for i := 0; i < ty.NumField(); i++ { - C.tableAppendColumn(t._hwnd, C.int(i), toUTF16(ty.Field(i).Name)) + C.tableAppendColumn(t.hwnd, C.int(i), toUTF16(ty.Field(i).Name)) } t.colcount = C.int(ty.NumField()) return t @@ -56,25 +61,25 @@ func (t *table) Unlock() { Do(func() { t.RLock() defer t.RUnlock() - C.tableUpdate(t._hwnd, C.int(reflect.Indirect(reflect.ValueOf(t.data)).Len())) + C.tableUpdate(t.hwnd, C.int(reflect.Indirect(reflect.ValueOf(t.data)).Len())) }) }() } func (t *table) LoadImageList(il ImageList) { - il.apply(t._hwnd, C.msgLoadImageList) + il.apply(t.hwnd, C.msgLoadImageList) } func (t *table) Selected() int { t.RLock() defer t.RUnlock() - return int(C.tableSelectedItem(t._hwnd)) + return int(C.tableSelectedItem(t.hwnd)) } func (t *table) Select(index int) { t.RLock() defer t.RUnlock() - C.tableSelectItem(t._hwnd, C.intptr_t(index)) + C.tableSelectItem(t.hwnd, C.intptr_t(index)) } func (t *table) OnSelected(f func()) { @@ -144,7 +149,7 @@ func (t *table) autoresize() { t.RLock() defer t.RUnlock() if !t.noautosize { - C.tableAutosizeColumns(t._hwnd, t.colcount) + C.tableAutosizeColumns(t.hwnd, t.colcount) } } @@ -167,7 +172,7 @@ func tableSetHot(data unsafe.Pointer, row C.int, col C.int) { t.hotrow = row t.hotcol = col if redraw { - C.tableUpdate(t._hwnd, C.int(reflect.Indirect(reflect.ValueOf(t.data)).Len())) + C.tableUpdate(t.hwnd, C.int(reflect.Indirect(reflect.ValueOf(t.data)).Len())) } } @@ -176,7 +181,7 @@ func tablePushed(data unsafe.Pointer, row C.int, col C.int) { t := (*table)(data) t.pushedrow = row t.pushedcol = col - C.tableUpdate(t._hwnd, C.int(reflect.Indirect(reflect.ValueOf(t.data)).Len())) + C.tableUpdate(t.hwnd, C.int(reflect.Indirect(reflect.ValueOf(t.data)).Len())) } //export tableToggled @@ -211,18 +216,6 @@ func tableSelectionChanged(data unsafe.Pointer) { t.selected.fire() } -func (t *table) hwnd() C.HWND { - return t._hwnd -} - -func (t *table) setParent(p *controlParent) { - basesetParent(t, p) -} - -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 // there IS a message LVM_APPROXIMATEVIEWRECT that can do calculations, but it doesn't seem to work right when asked to base its calculations on the current width/height on Windows and wine... @@ -230,17 +223,13 @@ const ( tableHeight = 50 ) -func (t *table) preferredSize(d *sizing) (width, height int) { +func (t *table) xpreferredSize(d *sizing) (width, height int) { return fromdlgunitsX(tableWidth, d), fromdlgunitsY(tableHeight, d) } -func (t *table) commitResize(a *allocation, d *sizing) { - basecommitResize(t, a, d) +func (t *table) xresize(x int, y int, width int, height int, d *sizing) { + t.chainresize(x, y, width, height, d) t.RLock() defer t.RUnlock() t.autoresize() } - -func (t *table) getAuxResizeInfo(d *sizing) { - basegetAuxResizeInfo(t, d) -} diff --git a/textfield_darwin.go b/textfield_darwin.go index 3270cde..bf63073 100644 --- a/textfield_darwin.go +++ b/textfield_darwin.go @@ -10,17 +10,20 @@ import ( import "C" type textfield struct { - _id C.id + *controlSingleObject changed *event invalid C.id + chainpreferredSize func(d *sizing) (int, int) } func finishNewTextField(id C.id) *textfield { t := &textfield{ - _id: id, + controlSingleObject: newControlSingleObject(id), changed: newEvent(), } - C.textfieldSetDelegate(t._id, unsafe.Pointer(t)) + C.textfieldSetDelegate(t.id, unsafe.Pointer(t)) + t.chainpreferredSize = t.fpreferredSize + t.fpreferredSize = t.xpreferredSize return t } @@ -33,13 +36,13 @@ func newPasswordField() *textfield { } func (t *textfield) Text() string { - return C.GoString(C.textfieldText(t._id)) + return C.GoString(C.textfieldText(t.id)) } func (t *textfield) SetText(text string) { ctext := C.CString(text) defer C.free(unsafe.Pointer(ctext)) - C.textfieldSetText(t._id, ctext) + C.textfieldSetText(t.id, ctext) } func (t *textfield) OnChanged(f func()) { @@ -56,7 +59,7 @@ func (t *textfield) Invalid(reason string) { } creason := C.CString(reason) defer C.free(unsafe.Pointer(creason)) - t.invalid = C.textfieldOpenInvalidPopover(t._id, creason) + t.invalid = C.textfieldOpenInvalidPopover(t.id, creason) } //export textfieldChanged @@ -65,28 +68,8 @@ func textfieldChanged(data unsafe.Pointer) { t.changed.fire() } -func (t *textfield) id() C.id { - return t._id -} - -func (t *textfield) setParent(p *controlParent) { - basesetParent(t, p) -} - -func (t *textfield) allocate(x int, y int, width int, height int, d *sizing) []*allocation { - return baseallocate(t, x, y, width, height, d) -} - -func (t *textfield) preferredSize(d *sizing) (width, height int) { - _, height = basepreferredSize(t, d) +func (t *textfield) xpreferredSize(d *sizing) (width, height int) { + _, height = t.chainpreferredSize(d) // the returned width is based on the contents; use this instead return C.textfieldWidth, height } - -func (t *textfield) commitResize(a *allocation, d *sizing) { - basecommitResize(t, a, d) -} - -func (t *textfield) getAuxResizeInfo(d *sizing) { - basegetAuxResizeInfo(t, d) -} diff --git a/textfield_unix.go b/textfield_unix.go index 33898d3..6535d39 100644 --- a/textfield_unix.go +++ b/textfield_unix.go @@ -18,7 +18,7 @@ import ( import "C" type textfield struct { - _widget *C.GtkWidget + *controlSingleWidget entry *C.GtkEntry changed *event } @@ -26,12 +26,12 @@ type textfield struct { func startNewTextField() *textfield { widget := C.gtk_entry_new() t := &textfield{ - _widget: widget, + controlSingleWidget: newControlSingleWidget(widget), entry: (*C.GtkEntry)(unsafe.Pointer(widget)), changed: newEvent(), } g_signal_connect( - C.gpointer(unsafe.Pointer(t._widget)), + C.gpointer(unsafe.Pointer(t.widget)), "changed", C.GCallback(C.textfieldChanged), C.gpointer(unsafe.Pointer(t))) @@ -71,7 +71,7 @@ func (t *textfield) Invalid(reason string) { creason := togstr(reason) defer freegstr(creason) C.gtk_entry_set_icon_tooltip_text(t.entry, C.GTK_ENTRY_ICON_SECONDARY, creason) - C.gtk_widget_error_bell(t._widget) + C.gtk_widget_error_bell(t.widget) } //export textfieldChanged @@ -79,27 +79,3 @@ func textfieldChanged(editable *C.GtkEditable, data C.gpointer) { t := (*textfield)(unsafe.Pointer(data)) t.changed.fire() } - -func (t *textfield) widget() *C.GtkWidget { - return t._widget -} - -func (t *textfield) setParent(p *controlParent) { - basesetParent(t, p) -} - -func (t *textfield) allocate(x int, y int, width int, height int, d *sizing) []*allocation { - return baseallocate(t, x, y, width, height, d) -} - -func (t *textfield) preferredSize(d *sizing) (width, height int) { - return basepreferredSize(t, d) -} - -func (t *textfield) commitResize(a *allocation, d *sizing) { - basecommitResize(t, a, d) -} - -func (t *textfield) getAuxResizeInfo(d *sizing) { - basegetAuxResizeInfo(t, d) -} diff --git a/textfield_windows.go b/textfield_windows.go index 16ce9d2..76ec882 100644 --- a/textfield_windows.go +++ b/textfield_windows.go @@ -10,8 +10,7 @@ import ( import "C" type textfield struct { - _hwnd C.HWND - _textlen C.LONG + *controlSingleHWNDWithText changed *event } @@ -22,11 +21,12 @@ func startNewTextField(style C.DWORD) *textfield { style|C.textfieldStyle, C.textfieldExtStyle) // WS_EX_CLIENTEDGE without WS_BORDER will show the canonical visual styles border (thanks to MindChild in irc.efnet.net/#winprog) t := &textfield{ - _hwnd: hwnd, + controlSingleHWNDWithText: newControlSingleHWNDWithText(hwnd), changed: newEvent(), } - C.controlSetControlFont(t._hwnd) - C.setTextFieldSubclass(t._hwnd, unsafe.Pointer(t)) + t.fpreferredSize = t.xpreferredSize + C.controlSetControlFont(t.hwnd) + C.setTextFieldSubclass(t.hwnd, unsafe.Pointer(t)) return t } @@ -39,11 +39,11 @@ func newPasswordField() *textfield { } func (t *textfield) Text() string { - return baseText(t) + return t.text() } func (t *textfield) SetText(text string) { - baseSetText(t, text) + t.setText(text) } func (t *textfield) OnChanged(f func()) { @@ -52,10 +52,10 @@ func (t *textfield) OnChanged(f func()) { func (t *textfield) Invalid(reason string) { if reason == "" { - C.textfieldHideInvalidBalloonTip(t._hwnd) + C.textfieldHideInvalidBalloonTip(t.hwnd) return } - C.textfieldSetAndShowInvalidBalloonTip(t._hwnd, toUTF16(reason)) + C.textfieldSetAndShowInvalidBalloonTip(t.hwnd, toUTF16(reason)) } //export textfieldChanged @@ -64,40 +64,12 @@ func textfieldChanged(data unsafe.Pointer) { t.changed.fire() } -func (t *textfield) hwnd() C.HWND { - return t._hwnd -} - -func (t *textfield) textlen() C.LONG { - return t._textlen -} - -func (t *textfield) settextlen(len C.LONG) { - t._textlen = len -} - -func (t *textfield) setParent(p *controlParent) { - basesetParent(t, p) -} - -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) preferredSize(d *sizing) (width, height int) { +func (t *textfield) xpreferredSize(d *sizing) (width, height int) { return fromdlgunitsX(textfieldWidth, d), fromdlgunitsY(textfieldHeight, d) } - -func (t *textfield) commitResize(a *allocation, d *sizing) { - basecommitResize(t, a, d) -} - -func (t *textfield) getAuxResizeInfo(d *sizing) { - basegetAuxResizeInfo(t, d) -} diff --git a/winapi_windows.h b/winapi_windows.h index 83f7822..e6858e5 100644 --- a/winapi_windows.h +++ b/winapi_windows.h @@ -98,7 +98,7 @@ extern LRESULT getWindowTextLen(HWND); extern void getWindowText(HWND, WPARAM, LPWSTR); extern void setWindowText(HWND, LPWSTR); extern void updateWindow(HWND); -extern void *getWindowData(HWND, UINT, WPARAM, LPARAM, LRESULT *, void (*)(void *, HWND)); +extern void *getWindowData(HWND, UINT, WPARAM, LPARAM, LRESULT *); extern BOOL sharedWndProc(HWND, UINT, WPARAM, LPARAM, LRESULT *); extern void paintControlBackground(HWND, HDC); @@ -122,8 +122,10 @@ extern intptr_t tableSelectedItem(HWND); extern void tableSelectItem(HWND, intptr_t); // container_windows.c +#define containerclass L"gouicontainer" extern DWORD makeContainerWindowClass(char **); -extern HWND newContainer(void *); +extern HWND newContainer(); +extern RECT containerBounds(HWND); extern void calculateBaseUnits(HWND, int *, int *, LONG *); // area_windows.c diff --git a/window.go b/window.go index dc98c1f..92b7080 100644 --- a/window.go +++ b/window.go @@ -26,6 +26,11 @@ type Window interface { // If this handler returns false, the Window is not closed. OnClosing(func() bool) + // Margined and SetMargined get and set whether the contents of the Window have a margin around them. + // The size of the margin is platform-dependent. + Margined() bool + SetMargined(margined bool) + windowDialog } diff --git a/window_darwin.go b/window_darwin.go index bb3214f..e0804be 100644 --- a/window_darwin.go +++ b/window_darwin.go @@ -14,7 +14,10 @@ type window struct { closing *event - *container + child Control + container *container + + margined bool } func newWindow(title string, width int, height int, control Control) *window { @@ -25,10 +28,13 @@ func newWindow(title string, width int, height int, control Control) *window { w := &window{ id: id, closing: newEvent(), - container: newContainer(control), + child: control, + container: newContainer(), } C.windowSetDelegate(w.id, unsafe.Pointer(w)) C.windowSetContentView(w.id, w.container.id) + w.child.setParent(w.container.parent()) + // trigger an initial resize return w } @@ -44,6 +50,9 @@ func (w *window) SetTitle(title string) { func (w *window) Show() { C.windowShow(w.id) + // trigger an initial resize + // TODO fine-tune this + windowResized(unsafe.Pointer(w)) } func (w *window) Hide() { @@ -58,6 +67,14 @@ func (w *window) OnClosing(e func() bool) { w.closing.setbool(e) } +func (w *window) Margined() bool { + return w.margined +} + +func (w *window) SetMargined(margined bool) { + w.margined = margined +} + //export windowClosing func windowClosing(xw unsafe.Pointer) C.BOOL { w := (*window)(unsafe.Pointer(xw)) @@ -67,3 +84,11 @@ func windowClosing(xw unsafe.Pointer) C.BOOL { } return C.NO } + +//export windowResized +func windowResized(data unsafe.Pointer) { + w := (*window)(data) + a := w.container.allocation(w.margined) + d := w.beginResize() + w.child.resize(int(a.x), int(a.y), int(a.width), int(a.height), d) +} diff --git a/window_darwin.m b/window_darwin.m index 21287cf..b156392 100644 --- a/window_darwin.m +++ b/window_darwin.m @@ -20,6 +20,11 @@ return windowClosing(self->gowin); } +- (void)windowDidResize:(NSNotification *)note +{ + windowResized(self->gowin); +} + @end id newWindow(intptr_t width, intptr_t height) diff --git a/window_unix.go b/window_unix.go index a220fb0..6b15bc1 100644 --- a/window_unix.go +++ b/window_unix.go @@ -10,6 +10,7 @@ import ( // #include "gtk_unix.h" // extern gboolean windowClosing(GtkWidget *, GdkEvent *, gpointer); +// extern void windowResized(GtkWidget *, GdkRectangle *, gpointer); import "C" type window struct { @@ -22,7 +23,10 @@ type window struct { closing *event - *container + child Control + container *container + + margined bool } func newWindow(title string, width int, height int, control Control) *window { @@ -35,6 +39,7 @@ func newWindow(title string, width int, height int, control Control) *window { bin: (*C.GtkBin)(unsafe.Pointer(widget)), window: (*C.GtkWindow)(unsafe.Pointer(widget)), closing: newEvent(), + child: control, } C.gtk_window_set_title(w.window, ctitle) g_signal_connect( @@ -43,8 +48,15 @@ func newWindow(title string, width int, height int, control Control) *window { C.GCallback(C.windowClosing), C.gpointer(unsafe.Pointer(w))) C.gtk_window_resize(w.window, C.gint(width), C.gint(height)) - w.container = newContainer(control) + w.container = newContainer() + w.child.setParent(w.container.parent()) w.container.setParent(&controlParent{w.wc}) + // notice that we connect this to the container + g_signal_connect_after( // so we get it after the child container has been allocated + C.gpointer(unsafe.Pointer(w.container.widget)), + "size-allocate", + C.GCallback(C.windowResized), + C.gpointer(unsafe.Pointer(w))) // for dialogs; otherwise, they will be modal to all windows, not just this one w.group = C.gtk_window_group_new() C.gtk_window_group_add_window(w.group, w.window) @@ -77,6 +89,14 @@ func (w *window) OnClosing(e func() bool) { w.closing.setbool(e) } +func (w *window) Margined() bool { + return w.margined +} + +func (w *window) SetMargined(margined bool) { + w.margined = margined +} + //export windowClosing func windowClosing(wid *C.GtkWidget, e *C.GdkEvent, data C.gpointer) C.gboolean { w := (*window)(unsafe.Pointer(data)) @@ -86,3 +106,11 @@ func windowClosing(wid *C.GtkWidget, e *C.GdkEvent, data C.gpointer) C.gboolean } return C.GDK_EVENT_STOP // keeps window alive } + +//export windowResized +func windowResized(wid *C.GtkWidget, r *C.GdkRectangle, data C.gpointer) { + w := (*window)(unsafe.Pointer(data)) + a := w.container.allocation(w.margined) + d := w.beginResize() + w.child.resize(int(a.x), int(a.y), int(a.width), int(a.height), d) +} diff --git a/window_windows.c b/window_windows.c index 2d047b0..a4eefb3 100644 --- a/window_windows.c +++ b/window_windows.c @@ -13,7 +13,7 @@ static LRESULT CALLBACK windowWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARA RECT r; LRESULT lResult; - data = (void *) getWindowData(hwnd, uMsg, wParam, lParam, &lResult, storeWindowHWND); + data = (void *) getWindowData(hwnd, uMsg, wParam, lParam, &lResult); if (data == NULL) return lResult; if (sharedWndProc(hwnd, uMsg, wParam, lParam, &lResult)) diff --git a/window_windows.go b/window_windows.go index e20206e..8537be8 100644 --- a/window_windows.go +++ b/window_windows.go @@ -17,7 +17,8 @@ type window struct { closing *event - *container + child Control + margined bool } func makeWindowWindowClass() error { @@ -32,19 +33,15 @@ func makeWindowWindowClass() error { func newWindow(title string, width int, height int, control Control) *window { w := &window{ - // hwnd set in WM_CREATE handler closing: newEvent(), - container: newContainer(control), - } - hwnd := C.newWindow(toUTF16(title), C.int(width), C.int(height), unsafe.Pointer(w)) - if hwnd != w.hwnd { - panic(fmt.Errorf("inconsistency: hwnd returned by CreateWindowEx() (%p) and hwnd stored in Window (%p) differ", hwnd, w.hwnd)) + child: control, } + w.hwnd = C.newWindow(toUTF16(title), C.int(width), C.int(height), unsafe.Pointer(w)) hresult := C.EnableThemeDialogTexture(w.hwnd, C.ETDT_ENABLE|C.ETDT_USETABTEXTURE) if hresult != C.S_OK { panic(fmt.Errorf("error setting tab background texture on Window; HRESULT: 0x%X", hresult)) } - w.container.setParent(w.hwnd) + w.child.setParent(&controlParent{w.hwnd}) return w } @@ -78,17 +75,22 @@ func (w *window) OnClosing(e func() bool) { w.closing.setbool(e) } -//export storeWindowHWND -func storeWindowHWND(data unsafe.Pointer, hwnd C.HWND) { - w := (*window)(data) - w.hwnd = hwnd +func (w *window) Margined() bool { + return w.margined +} + +func (w *window) SetMargined(margined bool) { + w.margined = margined } //export windowResize func windowResize(data unsafe.Pointer, r *C.RECT) { w := (*window)(data) - // the origin of the window's content area is always (0, 0), but let's use the values from the RECT just to be safe - w.container.move(r) + d := w.beginResize() + if w.margined { + marginRectDLU(r, marginDialogUnits, marginDialogUnits, marginDialogUnits, marginDialogUnits, d) + } + w.child.resize(int(r.left), int (r.top), int(r.right - r.left), int(r.bottom - r.top), d) } //export windowClosing diff --git a/yz_repaint_test.go b/yz_repaint_test.go index 8fca64b..8ea3cde 100644 --- a/yz_repaint_test.go +++ b/yz_repaint_test.go @@ -52,6 +52,7 @@ func newRepainter(times int) *repainter { grid.Add(r.repaint, nil, South, true, Fill, true, Fill, 1, 1) grid.Add(r.all, nil, South, true, Center, false, LeftTop, 1, 1) r.grid = grid + r.grid.SetPadded(*spaced) return r } diff --git a/zz_test.go b/zz_test.go index a7b0670..3f2f079 100644 --- a/zz_test.go +++ b/zz_test.go @@ -18,6 +18,25 @@ import ( var closeOnClick = flag.Bool("close", false, "close on click") var smallWindow = flag.Bool("small", false, "open a small window (test Mac OS X initial control sizing)") +var spaced = flag.Bool("spaced", false, "enable spacing") + +func newHorizontalStack(c ...Control) Stack { + s := NewHorizontalStack(c...) + s.SetPadded(*spaced) + return s +} + +func newVerticalStack(c ...Control) Stack { + s := NewVerticalStack(c...) + s.SetPadded(*spaced) + return s +} + +func newSimpleGrid(n int, c ...Control) SimpleGrid { + g := NewSimpleGrid(n, c...) + g.SetPadded(*spaced) + return g +} type dtype struct { Name string @@ -96,7 +115,7 @@ func (tw *testwin) addfe() { tw.felabel.SetText(t.String()) }) }) - tw.felabel = NewStandaloneLabel("") + tw.felabel = NewLabel("") tw.festop = NewButton("Stop") tw.festop.OnClicked(func() { if tw.fe != nil { @@ -117,8 +136,8 @@ func (tw *testwin) addfe() { tw.openbtn.OnClicked(func() { OpenFile(tw.w, tw.openFile) }) - tw.fnlabel = NewStandaloneLabel("") - tw.festack = NewVerticalStack(tw.festart, + tw.fnlabel = NewLabel("") + tw.festack = newVerticalStack(tw.festart, tw.felabel, tw.festop, NewCheckbox("This is a checkbox test"), @@ -129,7 +148,7 @@ func (tw *testwin) addfe() { tw.openbtn, tw.fnlabel) tw.festack.SetStretchy(4) tw.festack.SetStretchy(6) - tw.festack = NewHorizontalStack(tw.festack, Space()) + tw.festack = newHorizontalStack(tw.festack, Space()) tw.festack.SetStretchy(0) tw.festack.SetStretchy(1) tw.t.Append("Foreign Events", tw.festack) @@ -138,6 +157,7 @@ func (tw *testwin) addfe() { func (tw *testwin) make(done chan struct{}) { tw.t = NewTab() tw.w = NewWindow("Hello", 320, 240, tw.t) + tw.w.SetMargined(*spaced) tw.w.OnClosing(func() bool { if *closeOnClick { panic("window closed normally in close on click mode (should not happen)") @@ -171,12 +191,14 @@ func (tw *testwin) make(done chan struct{}) { tw.group2 = NewGroup("Group", NewButton("Button in Group")) tw.t.Append("Empty Group", NewGroup("Group", Space())) tw.t.Append("Filled Group", tw.group2) - tw.group = NewGroup("Group", NewVerticalStack(NewCheckbox("Checkbox in Group"))) + tw.group2.SetMargined(*spaced) + tw.group = NewGroup("Group", newVerticalStack(NewCheckbox("Checkbox in Group"))) + tw.group.SetMargined(*spaced) tw.t.Append("Group", tw.group) - tw.simpleGrid = NewSimpleGrid(3, + tw.simpleGrid = newSimpleGrid(3, NewLabel("0,0"), NewTextField(), NewLabel("0,2"), NewButton("1,0"), NewButton("1,1"), NewButton("1,2"), - NewLabel("2,0"), NewTextField(), NewStandaloneLabel("2,2")) + NewLabel("2,0"), NewTextField(), NewLabel("2,2")) tw.simpleGrid.SetFilling(2, 1) tw.simpleGrid.SetFilling(1, 2) tw.simpleGrid.SetStretchy(1, 1) @@ -189,33 +211,33 @@ func (tw *testwin) make(done chan struct{}) { tw.t.Append("Space", Space()) tw.a = NewArea(200, 200, &areaHandler{false}) tw.t.Append("Area", tw.a) - tw.spw = NewHorizontalStack( + tw.spw = newHorizontalStack( NewButton("hello"), NewCheckbox("hello"), NewTextField(), NewPasswordField(), NewTable(reflect.TypeOf(struct{ A, B, C int }{})), - NewStandaloneLabel("hello")) + NewLabel("hello")) tw.t.Append("Pref Width", tw.spw) - tw.sph = NewVerticalStack( + tw.sph = newVerticalStack( NewButton("hello"), NewCheckbox("hello"), NewTextField(), NewPasswordField(), NewTable(reflect.TypeOf(struct{ A, B, C int }{})), - NewStandaloneLabel("hello ÉÀÔ")) + NewLabel("hello ÉÀÔ")) tw.t.Append("Pref Height", tw.sph) - stack1 := NewHorizontalStack(NewLabel("Test"), NewTextField()) + stack1 := newHorizontalStack(NewLabel("Test"), NewTextField()) stack1.SetStretchy(1) - stack2 := NewHorizontalStack(NewLabel("ÉÀÔ"), NewTextField()) + stack2 := newHorizontalStack(NewLabel("ÉÀÔ"), NewTextField()) stack2.SetStretchy(1) - stack3 := NewHorizontalStack(NewLabel("Test 2"), + stack3 := newHorizontalStack(NewLabel("Test 2"), NewTable(reflect.TypeOf(struct{ A, B, C int }{}))) stack3.SetStretchy(1) - tw.s = NewVerticalStack(stack1, stack2, stack3) + tw.s = newVerticalStack(stack1, stack2, stack3) tw.s.SetStretchy(2) tw.t.Append("Stack", tw.s) - tw.l = NewStandaloneLabel("hello") + tw.l = NewLabel("hello") tw.t.Append("Label", tw.l) tw.table = NewTable(reflect.TypeOf(ddata[0])) tw.table.Lock() @@ -248,7 +270,7 @@ func (tw *testwin) make(done chan struct{}) { tw.w.Show() if *smallWindow { tw.wsmall = NewWindow("Small", 80, 80, - NewVerticalStack( + newVerticalStack( NewButton("Small"), NewButton("Small 2"), NewArea(200, 200, &areaHandler{true}))) @@ -262,7 +284,6 @@ var tw *testwin // because Cocoa hates being run off the main thread, even if it's run exclusively off the main thread func init() { - flag.BoolVar(&spaced, "spaced", false, "enable spacing") flag.Parse() go func() { tw = new(testwin)