Implemented Tab on Windows.
This commit is contained in:
parent
d515bd74c6
commit
9ea22218ff
|
@ -35,7 +35,7 @@ DWORD initCommonControls(LPCWSTR manifest, char **errmsg)
|
||||||
|
|
||||||
ZeroMemory(&icc, sizeof (INITCOMMONCONTROLSEX));
|
ZeroMemory(&icc, sizeof (INITCOMMONCONTROLSEX));
|
||||||
icc.dwSize = sizeof (INITCOMMONCONTROLSEX);
|
icc.dwSize = sizeof (INITCOMMONCONTROLSEX);
|
||||||
icc.dwICC = ICC_PROGRESS_CLASS;
|
icc.dwICC = ICC_PROGRESS_CLASS | ICC_TAB_CLASSES;
|
||||||
|
|
||||||
comctl32 = LoadLibraryW(L"comctl32.dll");
|
comctl32 = LoadLibraryW(L"comctl32.dll");
|
||||||
if (comctl32 == NULL) {
|
if (comctl32 == NULL) {
|
||||||
|
|
|
@ -0,0 +1,64 @@
|
||||||
|
/* 25 july 2014 */
|
||||||
|
|
||||||
|
#include "winapi_windows.h"
|
||||||
|
#include "_cgo_export.h"
|
||||||
|
|
||||||
|
/* provided for cgo's benefit */
|
||||||
|
LPCWSTR xWC_TABCONTROL = WC_TABCONTROL;
|
||||||
|
|
||||||
|
static LRESULT CALLBACK tabSubProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam, UINT_PTR id, DWORD_PTR data)
|
||||||
|
{
|
||||||
|
NMHDR *nmhdr = (NMHDR *) lParam;
|
||||||
|
LRESULT r;
|
||||||
|
|
||||||
|
switch (uMsg) {
|
||||||
|
case msgNOTIFY:
|
||||||
|
switch (nmhdr->code) {
|
||||||
|
case TCN_SELCHANGING:
|
||||||
|
r = SendMessageW(hwnd, TCM_GETCURSEL, 0, 0);
|
||||||
|
if (r == (LRESULT) -1) /* no tab currently selected */
|
||||||
|
return FALSE;
|
||||||
|
tabChanging((void *) data, r);
|
||||||
|
return FALSE; /* allow change */
|
||||||
|
case TCN_SELCHANGE:
|
||||||
|
tabChanged((void *) data, SendMessageW(hwnd, TCM_GETCURSEL, 0, 0));
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return (*fv_DefSubclassProc)(hwnd, uMsg, wParam, lParam);
|
||||||
|
case WM_NCDESTROY:
|
||||||
|
if ((*fv_RemoveWindowSubclass)(hwnd, tabSubProc, id) == FALSE)
|
||||||
|
xpanic("error removing Tab subclass (which was for its own event handler)", GetLastError());
|
||||||
|
return (*fv_DefSubclassProc)(hwnd, uMsg, wParam, lParam);
|
||||||
|
default:
|
||||||
|
return (*fv_DefSubclassProc)(hwnd, uMsg, wParam, lParam);
|
||||||
|
}
|
||||||
|
xmissedmsg("Tab", "tabSubProc()", uMsg);
|
||||||
|
return 0; /* unreached */
|
||||||
|
}
|
||||||
|
|
||||||
|
void setTabSubclass(HWND hwnd, void *data)
|
||||||
|
{
|
||||||
|
if ((*fv_SetWindowSubclass)(hwnd, tabSubProc, 0, (DWORD_PTR) data) == FALSE)
|
||||||
|
xpanic("error subclassing Tab to give it its own event handler", GetLastError());
|
||||||
|
}
|
||||||
|
|
||||||
|
void tabAppend(HWND hwnd, LPCWSTR name)
|
||||||
|
{
|
||||||
|
TCITEM item;
|
||||||
|
LRESULT n;
|
||||||
|
|
||||||
|
ZeroMemory(&item, sizeof (TCITEM));
|
||||||
|
item.mask = TCIF_TEXT;
|
||||||
|
/* TODO the C means const; change everything to use LPWSTR instead */
|
||||||
|
item.pszText = name;
|
||||||
|
/* MSDN's example code uses the first invalid index directly for this */
|
||||||
|
n = SendMessageW(hwnd, TCM_GETITEMCOUNT, 0, 0);
|
||||||
|
if (SendMessageW(hwnd, TCM_INSERTITEM, (WPARAM) n, (LPARAM) (&item)) == (LRESULT) -1)
|
||||||
|
xpanic("error adding tab to Tab", GetLastError());
|
||||||
|
}
|
||||||
|
|
||||||
|
extern void tabGetContentRect(HWND hwnd, RECT *r)
|
||||||
|
{
|
||||||
|
/* not &r; already a pointer (thanks MindChild in irc.efnet.net/#winprog for spotting my failure) */
|
||||||
|
SendMessageW(hwnd, TCM_ADJUSTRECT, FALSE, (LPARAM) r);
|
||||||
|
}
|
|
@ -0,0 +1,93 @@
|
||||||
|
// 25 july 2014
|
||||||
|
|
||||||
|
package ui
|
||||||
|
|
||||||
|
import (
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
// #include "winapi_windows.h"
|
||||||
|
import "C"
|
||||||
|
|
||||||
|
/*
|
||||||
|
On Windows, container controls are just regular controls; their children have to be children of the parent window, and changing the contents of a switching container (such as a tab control) must be done manually. Mind the odd code here.
|
||||||
|
|
||||||
|
TODO
|
||||||
|
- make sure all tabs cannot be deselected (that is, make sure the current tab can never have index -1)
|
||||||
|
*/
|
||||||
|
|
||||||
|
type tab struct {
|
||||||
|
*widgetbase
|
||||||
|
tabs []Control
|
||||||
|
curparent *window
|
||||||
|
}
|
||||||
|
|
||||||
|
func newTab() Tab {
|
||||||
|
w := newWidget(C.xWC_TABCONTROL,
|
||||||
|
C.TCS_TOOLTIPS | C.WS_TABSTOP,
|
||||||
|
0)
|
||||||
|
t := &tab{
|
||||||
|
widgetbase: w,
|
||||||
|
}
|
||||||
|
C.controlSetControlFont(w.hwnd)
|
||||||
|
C.setTabSubclass(w.hwnd, unsafe.Pointer(t))
|
||||||
|
return t
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *tab) unparent() {
|
||||||
|
t.widgetbase.unparent()
|
||||||
|
for _, c := range t.tabs {
|
||||||
|
c.unparent()
|
||||||
|
}
|
||||||
|
t.curparent = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *tab) parent(win *window) {
|
||||||
|
t.widgetbase.parent(win)
|
||||||
|
for _, c := range t.tabs {
|
||||||
|
c.parent(win)
|
||||||
|
}
|
||||||
|
t.curparent = win
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *tab) Append(name string, control Control) {
|
||||||
|
t.tabs = append(t.tabs, control)
|
||||||
|
if t.curparent == nil {
|
||||||
|
control.unparent()
|
||||||
|
} else {
|
||||||
|
control.parent(t.curparent)
|
||||||
|
}
|
||||||
|
C.tabAppend(t.hwnd, toUTF16(name))
|
||||||
|
}
|
||||||
|
|
||||||
|
//export tabChanging
|
||||||
|
func tabChanging(data unsafe.Pointer, current C.LRESULT) {
|
||||||
|
t := (*tab)(data)
|
||||||
|
t.tabs[int(current)].containerHide()
|
||||||
|
}
|
||||||
|
|
||||||
|
//export tabChanged
|
||||||
|
func tabChanged(data unsafe.Pointer, new C.LRESULT) {
|
||||||
|
t := (*tab)(data)
|
||||||
|
t.tabs[int(new)].containerShow()
|
||||||
|
}
|
||||||
|
|
||||||
|
// a tab control contains other controls; size appropriately
|
||||||
|
func (t *tab) allocate(x int, y int, width int, height int, d *sizing) []*allocation {
|
||||||
|
var r C.RECT
|
||||||
|
|
||||||
|
// first, append the tab control itself
|
||||||
|
a := t.widgetbase.allocate(x, y, width, height, d)
|
||||||
|
// now figure out what the rect for each child is
|
||||||
|
r.left = C.LONG(x) // load rect with existing values
|
||||||
|
r.top = C.LONG(y)
|
||||||
|
r.right = C.LONG(x + width)
|
||||||
|
r.bottom = C.LONG(y + height)
|
||||||
|
C.tabGetContentRect(t.hwnd, &r)
|
||||||
|
// and allocate
|
||||||
|
// don't allocate to just hte current tab; allocate to all tabs!
|
||||||
|
for _, c := range t.tabs {
|
||||||
|
a = append(a, c.allocate(int(r.left), int(r.top), int(r.right - r.left), int(r.bottom - r.top), d)...)
|
||||||
|
}
|
||||||
|
return a
|
||||||
|
}
|
|
@ -50,6 +50,17 @@ LRESULT forwardCommand(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
|
||||||
return DefWindowProcW(hwnd, uMsg, wParam, lParam);
|
return DefWindowProcW(hwnd, uMsg, wParam, lParam);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
LRESULT forwardNotify(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
|
||||||
|
{
|
||||||
|
NMHDR *nmhdr = (NMHDR *) lParam;
|
||||||
|
HWND control = nmhdr->hwndFrom;
|
||||||
|
|
||||||
|
/* don't generate an event if the control (if there is one) is unparented (a child of the message-only window) */
|
||||||
|
if (control != NULL && IsChild(msgwin, control) == 0)
|
||||||
|
return SendMessageW(control, msgNOTIFY, wParam, lParam);
|
||||||
|
return DefWindowProcW(hwnd, uMsg, wParam, lParam);
|
||||||
|
}
|
||||||
|
|
||||||
static LRESULT CALLBACK buttonSubProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam, UINT_PTR id, DWORD_PTR data)
|
static LRESULT CALLBACK buttonSubProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam, UINT_PTR id, DWORD_PTR data)
|
||||||
{
|
{
|
||||||
switch (uMsg) {
|
switch (uMsg) {
|
||||||
|
|
|
@ -37,6 +37,8 @@ static LRESULT CALLBACK msgwinproc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM l
|
||||||
switch (uMsg) {
|
switch (uMsg) {
|
||||||
case WM_COMMAND:
|
case WM_COMMAND:
|
||||||
return forwardCommand(hwnd, uMsg, wParam, lParam);
|
return forwardCommand(hwnd, uMsg, wParam, lParam);
|
||||||
|
case WM_NOTIFY:
|
||||||
|
return forwardNotify(hwnd, uMsg, wParam, lParam);
|
||||||
case msgRequest:
|
case msgRequest:
|
||||||
doissue((void *) lParam);
|
doissue((void *) lParam);
|
||||||
return 0;
|
return 0;
|
||||||
|
|
|
@ -8,7 +8,7 @@ import (
|
||||||
"unsafe"
|
"unsafe"
|
||||||
)
|
)
|
||||||
|
|
||||||
// #cgo LDFLAGS: -luser32 -lkernel32 -lgdi32
|
// #cgo LDFLAGS: -luser32 -lkernel32 -lgdi32 -luxtheme
|
||||||
// #include "winapi_windows.h"
|
// #include "winapi_windows.h"
|
||||||
import "C"
|
import "C"
|
||||||
|
|
||||||
|
|
|
@ -17,11 +17,13 @@
|
||||||
#include <windows.h>
|
#include <windows.h>
|
||||||
#include <commctrl.h>
|
#include <commctrl.h>
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
#include <uxtheme.h>
|
||||||
|
|
||||||
/* global messages unique to everything */
|
/* global messages unique to everything */
|
||||||
enum {
|
enum {
|
||||||
msgRequest = WM_APP + 1, /* + 1 just to be safe */
|
msgRequest = WM_APP + 1, /* + 1 just to be safe */
|
||||||
msgCOMMAND, /* WM_COMMAND proxy; see forwardCommand() in controls_windows.go */
|
msgCOMMAND, /* WM_COMMAND proxy; see forwardCommand() in controls_windows.go */
|
||||||
|
msgNOTIFY, /* WM_NOTIFY proxy */
|
||||||
};
|
};
|
||||||
|
|
||||||
/* uitask_windows.c */
|
/* uitask_windows.c */
|
||||||
|
@ -42,6 +44,7 @@ extern HWND newWidget(LPCWSTR, DWORD, DWORD);
|
||||||
extern void controlSetParent(HWND, HWND);
|
extern void controlSetParent(HWND, HWND);
|
||||||
extern void controlSetControlFont(HWND);
|
extern void controlSetControlFont(HWND);
|
||||||
extern LRESULT forwardCommand(HWND, UINT, WPARAM, LPARAM);
|
extern LRESULT forwardCommand(HWND, UINT, WPARAM, LPARAM);
|
||||||
|
extern LRESULT forwardNotify(HWND, UINT, WPARAM, LPARAM);
|
||||||
extern void setButtonSubclass(HWND, void *);
|
extern void setButtonSubclass(HWND, void *);
|
||||||
extern void setCheckboxSubclass(HWND, void *);
|
extern void setCheckboxSubclass(HWND, void *);
|
||||||
extern BOOL checkboxChecked(HWND);
|
extern BOOL checkboxChecked(HWND);
|
||||||
|
@ -77,4 +80,10 @@ extern void setWindowText(HWND, LPCWSTR);
|
||||||
extern void updateWindow(HWND);
|
extern void updateWindow(HWND);
|
||||||
extern void storelpParam(HWND, LPARAM);
|
extern void storelpParam(HWND, LPARAM);
|
||||||
|
|
||||||
|
/* containers_windows.go */
|
||||||
|
extern LPCWSTR xWC_TABCONTROL;
|
||||||
|
extern void setTabSubclass(HWND, void *);
|
||||||
|
extern void tabAppend(HWND, LPCWSTR);
|
||||||
|
extern void tabGetContentRect(HWND, RECT *);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -25,6 +25,8 @@ static LRESULT CALLBACK windowWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARA
|
||||||
switch (uMsg) {
|
switch (uMsg) {
|
||||||
case WM_COMMAND:
|
case WM_COMMAND:
|
||||||
return forwardCommand(hwnd, uMsg, wParam, lParam);
|
return forwardCommand(hwnd, uMsg, wParam, lParam);
|
||||||
|
case WM_NOTIFY:
|
||||||
|
return forwardNotify(hwnd, uMsg, wParam, lParam);
|
||||||
case WM_SIZE:
|
case WM_SIZE:
|
||||||
if (GetClientRect(hwnd, &r) == 0)
|
if (GetClientRect(hwnd, &r) == 0)
|
||||||
xpanic("error getting client rect for Window in WM_SIZE", GetLastError());
|
xpanic("error getting client rect for Window in WM_SIZE", GetLastError());
|
||||||
|
|
|
@ -44,6 +44,11 @@ func newWindow(title string, width int, height int) *window {
|
||||||
if hwnd != w.hwnd {
|
if hwnd != w.hwnd {
|
||||||
panic(fmt.Errorf("inconsistency: hwnd returned by CreateWindowEx() (%p) and hwnd stored in window (%p) differ", hwnd, w.hwnd))
|
panic(fmt.Errorf("inconsistency: hwnd returned by CreateWindowEx() (%p) and hwnd stored in window (%p) differ", hwnd, w.hwnd))
|
||||||
}
|
}
|
||||||
|
// TODO keep?
|
||||||
|
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))
|
||||||
|
}
|
||||||
return w
|
return w
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -20,7 +20,8 @@ func init() {
|
||||||
Do(func() {
|
Do(func() {
|
||||||
w := NewWindow("Hello", 320, 240)
|
w := NewWindow("Hello", 320, 240)
|
||||||
b := NewButton("There")
|
b := NewButton("There")
|
||||||
w.SetControl(b)
|
t := NewTab()
|
||||||
|
w.SetControl(t)
|
||||||
if *closeOnClick {
|
if *closeOnClick {
|
||||||
b.SetText("Click to Close")
|
b.SetText("Click to Close")
|
||||||
}
|
}
|
||||||
|
@ -40,14 +41,14 @@ func init() {
|
||||||
w.Close()
|
w.Close()
|
||||||
Stop()
|
Stop()
|
||||||
done <- struct{}{}
|
done <- struct{}{}
|
||||||
} else {
|
}
|
||||||
|
})
|
||||||
|
t.Append("Button", b)
|
||||||
c := NewCheckbox("You Should Now See Me Instead")
|
c := NewCheckbox("You Should Now See Me Instead")
|
||||||
w.SetControl(c)
|
t.Append("Checkbox", c)
|
||||||
c.OnClicked(func() {
|
c.OnClicked(func() {
|
||||||
w.SetTitle(fmt.Sprint(c.Checked()))
|
w.SetTitle(fmt.Sprint(c.Checked()))
|
||||||
})
|
})
|
||||||
}
|
|
||||||
})
|
|
||||||
w.Show()
|
w.Show()
|
||||||
})
|
})
|
||||||
<-done
|
<-done
|
||||||
|
|
Loading…
Reference in New Issue