diff --git a/src/dlangui/core/events.d b/src/dlangui/core/events.d index 24db5132..108c4c48 100644 --- a/src/dlangui/core/events.d +++ b/src/dlangui/core/events.d @@ -2,17 +2,14 @@ module dlangui.core.events; import std.conv; -enum MouseAction : ushort { - LButtonDown, - LButtonUp, - MButtonDown, - MButtonUp, - RButtonDown, - RButtonUp, +enum MouseAction : ubyte { + Cancel, + ButtonDown, // button is down + ButtonUp, // button is up + Move, // mouse pointer is moving Wheel, - Move, - Leave, - Hover + FocusIn, + FocusOut } enum MouseFlag : ushort { @@ -26,9 +23,11 @@ enum MouseFlag : ushort { } /// mouse button state details -struct ButtondDetails { +struct ButtonDetails { /// Clock.currStdTime() for down event of this button (0 if button is up). long _downTs; + /// Clock.currStdTime() for up event of this button (0 if button is still down). + long _upTs; /// x coordinates of down event short _downX; /// y coordinates of down event @@ -40,32 +39,59 @@ struct ButtondDetails { _downX = x; _downY = y; _downFlags = flags; + _upTs = 0; _downTs = std.datetime.Clock.currStdTime; } /// update for button up - void up() { - _downTs = 0; - _downX = 0; - _downY = 0; - _downFlags = 0; + void up(short x, short y, ushort flags) { + _upTs = std.datetime.Clock.currStdTime; } - @property bool isDown() { return downTs != 0; } + @property bool isDown() { return _downTs != 0 && _upTs == 0; } + /// returns button down state duration in hnsecs (1/10000 of second). + @property int downDuration() { + if (_downTs == 0) + return 0; + if (_downTs != 0 && _upTs != 0) + return cast(int)(_upTs - _downTs); + long ts = std.datetime.Clock.currStdTime; + return cast(int)(ts - _downTs); + } + @property short downX() { return _downX; } + @property short downY() { return _downY; } + @property ushort downFlags() { return _downFlags; } +} + +enum MouseButton : ubyte { + None, + Left, + Right, + Middle + //XButton1, // additional button + //XButton2, // additional button } class MouseEvent { + protected long _eventTimestamp; protected MouseAction _action; - protected ushort _flags; + protected MouseButton _button; protected short _x; protected short _y; - protected ButonDetails _lbutton; - protected ButonDetails _mbutton; - protected ButonDetails _rbutton; + protected ushort _flags; + protected ButtonDetails _lbutton; + protected ButtonDetails _mbutton; + protected ButtonDetails _rbutton; + @property ref ButtonDetails lbutton() { return _lbutton; } + @property ref ButtonDetails rbutton() { return _rbutton; } + @property ref ButtonDetails mbutton() { return _mbutton; } + @property MouseButton button() { return _button; } @property MouseAction action() { return _action; } @property ushort flags() { return _flags; } @property short x() { return _x; } @property short y() { return _y; } - this (MouseAction a, ushort f, short x, short y) { + this (MouseAction a, MouseButton b, ushort f, short x, short y) { + _eventTimestamp = std.datetime.Clock.currStdTime; _action = a; + _button = b; _flags = f; _x = x; _y = y; diff --git a/src/dlangui/core/types.d b/src/dlangui/core/types.d index d3ee5cd0..c3df2200 100644 --- a/src/dlangui/core/types.d +++ b/src/dlangui/core/types.d @@ -53,6 +53,14 @@ struct Rect { return false; return true; } + /// returns true if point is inside of this rectangle + bool isPointInside(Point pt) { + return pt.x >= left && pt.x < right && pt.y >= top && pt.y < bottom; + } + /// returns true if point is inside of this rectangle + bool isPointInside(int x, int y) { + return x >= left && x < right && y >= top && y < bottom; + } } /// character glyph diff --git a/src/dlangui/platforms/common/platform.d b/src/dlangui/platforms/common/platform.d index b2de4ef1..44910b7e 100644 --- a/src/dlangui/platforms/common/platform.d +++ b/src/dlangui/platforms/common/platform.d @@ -7,9 +7,9 @@ import std.file; private import dlangui.graphics.gldrawbuf; class Window { - int _dx; - int _dy; - Widget _mainWidget; + protected int _dx; + protected int _dy; + protected Widget _mainWidget; @property int width() { return _dx; } @property int height() { return _dy; } @property Widget mainWidget() { return _mainWidget; } diff --git a/src/dlangui/platforms/windows/winapp.d b/src/dlangui/platforms/windows/winapp.d index b2006b7f..6e1ad690 100644 --- a/src/dlangui/platforms/windows/winapp.d +++ b/src/dlangui/platforms/windows/winapp.d @@ -13,6 +13,7 @@ import dlangui.platforms.common.platform; import dlangui.platforms.windows.win32fonts; import dlangui.platforms.windows.win32drawbuf; import dlangui.widgets.styles; +import dlangui.widgets.widget; import dlangui.graphics.drawbuf; import dlangui.graphics.images; import dlangui.graphics.fonts; @@ -127,14 +128,17 @@ version (USE_OPENGL) { } class Win32Window : Window { - private HWND _hwnd; - HGLRC _hGLRC; // opengl context - HPALETTE _hPalette; + Win32Platform _platform; + HWND _hwnd; + version (USE_OPENGL) { + HGLRC _hGLRC; // opengl context + HPALETTE _hPalette; + } string _caption; Win32ColorDrawBuf _drawbuf; bool useOpengl; - this(string windowCaption, Window parent) { - import derelict.opengl3.wgl; + this(Win32Platform platform, string windowCaption, Window parent) { + _platform = platform; _caption = windowCaption; _hwnd = CreateWindow(toUTF16z(WIN_CLASS_NAME), // window class name toUTF16z(windowCaption), // window caption @@ -149,6 +153,7 @@ class Win32Window : Window { cast(void*)this); // creation parameters version (USE_OPENGL) { + import derelict.opengl3.wgl; /* initialize OpenGL rendering */ HDC hDC = GetDC(_hwnd); @@ -193,60 +198,6 @@ class Win32Window : Window { } } } - ~this() { - Log.d("Window destructor"); - version (USE_OPENGL) { - import derelict.opengl3.wgl; - if (_hGLRC) { - uninitShaders(); - wglMakeCurrent (null, null) ; - wglDeleteContext(_hGLRC); - _hGLRC = null; - } - } - if (_hwnd) - DestroyWindow(_hwnd); - _hwnd = null; - } - Win32ColorDrawBuf getDrawBuf() { - //RECT rect; - //GetClientRect(_hwnd, &rect); - //int dx = rect.right - rect.left; - //int dy = rect.bottom - rect.top; - if (_drawbuf is null) - _drawbuf = new Win32ColorDrawBuf(_dx, _dy); - else - _drawbuf.resize(_dx, _dy); - return _drawbuf; - } - override void show() { - ShowWindow(_hwnd, _cmdShow); - UpdateWindow(_hwnd); - } - override @property string windowCaption() { - return _caption; - } - override @property void windowCaption(string caption) { - _caption = caption; - SetWindowTextW(_hwnd, toUTF16z(_caption)); - } - void onCreate() { - Log.d("Window onCreate"); - } - void onDestroy() { - Log.d("Window onDestroy"); - } - - private void paintUsingGDI() { - PAINTSTRUCT ps; - HDC hdc = BeginPaint(_hwnd, &ps); - scope(exit) EndPaint(_hwnd, &ps); - - Win32ColorDrawBuf buf = getDrawBuf(); - buf.fill(0x808080); - onDraw(buf); - buf.drawTo(hdc, 0, 0); - } version (USE_OPENGL) { private void paintUsingOpenGL() { @@ -293,6 +244,63 @@ class Win32Window : Window { } } + ~this() { + Log.d("Window destructor"); + version (USE_OPENGL) { + import derelict.opengl3.wgl; + if (_hGLRC) { + uninitShaders(); + wglMakeCurrent (null, null) ; + wglDeleteContext(_hGLRC); + _hGLRC = null; + } + } + if (_hwnd) + DestroyWindow(_hwnd); + _hwnd = null; + } + Win32ColorDrawBuf getDrawBuf() { + //RECT rect; + //GetClientRect(_hwnd, &rect); + //int dx = rect.right - rect.left; + //int dy = rect.bottom - rect.top; + if (_drawbuf is null) + _drawbuf = new Win32ColorDrawBuf(_dx, _dy); + else + _drawbuf.resize(_dx, _dy); + return _drawbuf; + } + override void show() { + ShowWindow(_hwnd, _cmdShow); + UpdateWindow(_hwnd); + } + override @property string windowCaption() { + return _caption; + } + override @property void windowCaption(string caption) { + _caption = caption; + SetWindowTextW(_hwnd, toUTF16z(_caption)); + } + void onCreate() { + Log.d("Window onCreate"); + _platform.onWindowCreated(_hwnd, this); + } + void onDestroy() { + Log.d("Window onDestroy"); + _platform.onWindowDestroyed(_hwnd, this); + } + + private void paintUsingGDI() { + PAINTSTRUCT ps; + HDC hdc = BeginPaint(_hwnd, &ps); + scope(exit) EndPaint(_hwnd, &ps); + + Win32ColorDrawBuf buf = getDrawBuf(); + buf.fill(0x808080); + onDraw(buf); + buf.drawTo(hdc, 0, 0); + } + void onPaint() { Log.d("onPaint()"); version (USE_OPENGL) { @@ -306,18 +314,115 @@ class Win32Window : Window { } } - protected ButonDetails _lbutton; - protected ButonDetails _mbutton; - protected ButonDetails _rbutton; + protected ButtonDetails _lbutton; + protected ButtonDetails _mbutton; + protected ButtonDetails _rbutton; override bool onMouseEvent(MouseEvent event) { return false; } - bool onMouse(MouseEvent event) { - Log.d("MouseEvent ", event.action, " flags=", event.flags, " x=", event.x, " y=", event.y); - if (event.action == MouseAction.LButtonDown) - return true; + protected bool dispatchMouseEvent(Widget root, MouseEvent event) { + // only route mouse events to visible widgets + if (root.visibility != Visibility.Visible) + return false; + // offer event to children first + for (int i = 0; i < root.childCount; i++) { + Widget child = root.child(i); + if (dispatchMouseEvent(child, event)) + return true; + } + // if not processed by children, offer event to root + if (root.onMouseEvent(event)) { + Log.d("MouseEvent is processed"); + if (event.action == MouseAction.ButtonDown && _mouseCaptureWidget is null) { + Log.d("Setting active widget"); + _mouseCaptureWidget = root; + } + return true; + } + return false; + } + + protected Widget _mouseCaptureWidget; + bool dispatchMouseEvent(MouseEvent event) { + // ignore events if there is no root + if (_mainWidget is null) + return false; + // check if _mouseCaptureWidget still exists in child of root widget + if (_mouseCaptureWidget !is null && !_mainWidget.isChild(_mouseCaptureWidget)) + _mouseCaptureWidget = null; + bool res = false; + if (_mouseCaptureWidget !is null) { + // try to forward message directly to active widget + res = _mouseCaptureWidget.onMouseEvent(event); + } + if (_mouseCaptureWidget !is null && (event.flags & (MouseFlag.LButton | MouseFlag.MButton | MouseFlag.RButton)) == 0) { + // usable capturing - no more buttons pressed + Log.d("unsetting active widget"); + _mouseCaptureWidget = null; + } + if (res) + return res; + if (!res) { + res = dispatchMouseEvent(_mainWidget, event); + } + return res; + } + + bool onMouse(uint message, ushort flags, short x, short y) { + Log.d("Win32 Mouse Message ", message, " flags=", flags, " x=", x, " y=", y); + MouseButton button = MouseButton.None; + MouseAction action = MouseAction.ButtonDown; + ButtonDetails * pbuttonDetails = null; + switch (message) { + case WM_MOUSEMOVE: + action = MouseAction.Move; + break; + case WM_LBUTTONDOWN: + action = MouseAction.ButtonDown; + button = MouseButton.Left; + pbuttonDetails = &_lbutton; + break; + case WM_RBUTTONDOWN: + action = MouseAction.ButtonDown; + button = MouseButton.Right; + pbuttonDetails = &_rbutton; + break; + case WM_MBUTTONDOWN: + action = MouseAction.ButtonDown; + button = MouseButton.Middle; + pbuttonDetails = &_mbutton; + break; + case WM_LBUTTONUP: + action = MouseAction.ButtonUp; + button = MouseButton.Left; + pbuttonDetails = &_lbutton; + break; + case WM_RBUTTONUP: + action = MouseAction.ButtonUp; + button = MouseButton.Right; + pbuttonDetails = &_rbutton; + break; + case WM_MBUTTONUP: + action = MouseAction.ButtonUp; + button = MouseButton.Middle; + pbuttonDetails = &_mbutton; + break; + default: + // unsupported event + return false; + } + if (action == MouseAction.ButtonDown) { + pbuttonDetails.down(x, y, flags); + } else if (action == MouseAction.ButtonDown) { + pbuttonDetails.up(x, y, flags); + } + MouseEvent event = new MouseEvent(action, button, flags, x, y); + event.lbutton = _lbutton; + event.rbutton = _rbutton; + event.mbutton = _mbutton; + return dispatchMouseEvent(event); } } @@ -354,8 +459,32 @@ class Win32Platform : Platform { } return msg.wParam; } + private Win32Window[ulong] _windowMap; + /// add window to window map + void onWindowCreated(HWND hwnd, Win32Window window) { + _windowMap[cast(ulong)hwnd] = window; + } + /// remove window from window map, returns true if there are some more windows left in map + bool onWindowDestroyed(HWND hwnd, Win32Window window) { + Win32Window wnd = getWindow(hwnd); + if (wnd) { + _windowMap.remove(cast(ulong)hwnd); + destroy(window); + } + return _windowMap.length > 0; + } + /// returns number of currently active windows + @property int windowCount() { + return cast(int)_windowMap.length; + } + /// returns window instance by HWND + Win32Window getWindow(HWND hwnd) { + if ((cast(ulong)hwnd) in _windowMap) + return _windowMap[cast(ulong)hwnd]; + return null; + } override Window createWindow(string windowCaption, Window parent) { - return new Win32Window(windowCaption, parent); + return new Win32Window(this, windowCaption, parent); } } @@ -406,6 +535,8 @@ string[] splitCmdLine(string line) { return res; } +private __gshared Win32Platform platform; + int myWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int iCmdShow) { setFileLogger(std.stdio.File("ui.log", "w")); @@ -421,7 +552,7 @@ int myWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int _cmdShow = iCmdShow; _hInstance = hInstance; - Win32Platform platform = new Win32Platform(); + platform = new Win32Platform(); if (!platform.registerWndClass()) { MessageBoxA(null, "This program requires Windows NT!", "DLANGUI App".toStringz, MB_ICONERROR); return 0; @@ -499,8 +630,16 @@ LRESULT WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) { HDC hdc; RECT rect; + void * p = cast(void*)GetWindowLongPtr(hwnd, GWLP_USERDATA); - Win32Window window = p is null ? null : cast(Win32Window)(p); + Win32Window windowParam = p is null ? null : cast(Win32Window)(p); + Win32Window window = platform.getWindow(hwnd); + if (windowParam !is null && window !is null) + assert(window is windowParam); + if (window is null && windowParam !is null) { + Log.e("Cannot find window in map by HWND"); + } + switch (message) { case WM_CREATE: @@ -509,23 +648,31 @@ LRESULT WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) window = cast(Win32Window)pcreateStruct.lpCreateParams; void * ptr = cast(void*) window; SetWindowLongPtr(hwnd, GWLP_USERDATA, cast(LONG_PTR)ptr); + window._hwnd = hwnd; window.onCreate(); } - //PlaySoundA("hellowin.wav", NULL, SND_FILENAME | SND_ASYNC); + return 0; + case WM_DESTROY: + if (window !is null) + window.onDestroy(); + if (platform.windowCount == 0) + PostQuitMessage(0); return 0; case WM_WINDOWPOSCHANGED: { - WINDOWPOS * pos = cast(WINDOWPOS*)lParam; - GetClientRect(hwnd, &rect); - int dx = rect.right - rect.left; - int dy = rect.bottom - rect.top; - //window.onResize(pos.cx, pos.cy); - window.onResize(dx, dy); - InvalidateRect(hwnd, null, FALSE); - //UpdateWindow(hwnd); + if (window !is null) { + WINDOWPOS * pos = cast(WINDOWPOS*)lParam; + GetClientRect(hwnd, &rect); + int dx = rect.right - rect.left; + int dy = rect.bottom - rect.top; + //window.onResize(pos.cx, pos.cy); + window.onResize(dx, dy); + InvalidateRect(hwnd, null, FALSE); + } } return 0; case WM_ERASEBKGND: + // processed return 1; case WM_PAINT: { @@ -534,39 +681,21 @@ LRESULT WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) } return 0; // processed case WM_MOUSEMOVE: - if (window !is null) - window.onMouseEvent(new MouseEvent(MouseAction.Move, cast(ushort)wParam, cast(short)(lParam & 0xFFFF), cast(short)((lParam >> 16) & 0xFFFF))); - return 0; // processed case WM_LBUTTONDOWN: - if (window !is null) - window.onMouseEvent(new MouseEvent(MouseAction.LButtonDown, cast(ushort)wParam, cast(short)(lParam & 0xFFFF), cast(short)((lParam >> 16) & 0xFFFF))); - return 0; // processed case WM_MBUTTONDOWN: - if (window !is null) - window.onMouseEvent(new MouseEvent(MouseAction.MButtonDown, cast(ushort)wParam, cast(short)(lParam & 0xFFFF), cast(short)((lParam >> 16) & 0xFFFF))); - return 0; // processed case WM_RBUTTONDOWN: - if (window !is null) - window.onMouseEvent(new MouseEvent(MouseAction.RButtonDown, cast(ushort)wParam, cast(short)(lParam & 0xFFFF), cast(short)((lParam >> 16) & 0xFFFF))); - return 0; // processed case WM_LBUTTONUP: - if (window !is null) - window.onMouseEvent(new MouseEvent(MouseAction.LButtonUp, cast(ushort)wParam, cast(short)(lParam & 0xFFFF), cast(short)((lParam >> 16) & 0xFFFF))); - return 0; // processed case WM_MBUTTONUP: - if (window !is null) - window.onMouseEvent(new MouseEvent(MouseAction.MButtonUp, cast(ushort)wParam, cast(short)(lParam & 0xFFFF), cast(short)((lParam >> 16) & 0xFFFF))); - return 0; // processed case WM_RBUTTONUP: if (window !is null) - window.onMouseEvent(new MouseEvent(MouseAction.RButtonUp, cast(ushort)wParam, cast(short)(lParam & 0xFFFF), cast(short)((lParam >> 16) & 0xFFFF))); + window.onMouse(message, cast(ushort)wParam, cast(short)(lParam & 0xFFFF), cast(short)((lParam >> 16) & 0xFFFF)); return 0; // processed - case WM_DESTROY: - window.onDestroy(); - PostQuitMessage(0); - return 0; - + case WM_GETMINMAXINFO: + case WM_NCCREATE: + case WM_NCCALCSIZE: default: + //Log.d("Unhandled message ", message); + break; } return DefWindowProc(hwnd, message, wParam, lParam); diff --git a/src/dlangui/widgets/controls.d b/src/dlangui/widgets/controls.d index 6d026952..4367b9ec 100644 --- a/src/dlangui/widgets/controls.d +++ b/src/dlangui/widgets/controls.d @@ -17,11 +17,33 @@ class TextWidget : Widget { requestLayout(); return this; } + override void measure(int parentWidth, int parentHeight) { FontRef font = font(); Point sz = font.textSize(text); measuredContent(parentWidth, parentHeight, sz.x, sz.y); } + + bool onClick() { + // override it + Log.d("Button.onClick ", id); + return false; + } + + override bool onMouseEvent(MouseEvent event) { + if (event.action == MouseAction.ButtonDown && event.button == MouseButton.Left) { + setState(State.Pressed); + Log.d("Button state: ", state); + return true; + } + if (event.action == MouseAction.ButtonUp && event.button == MouseButton.Left) { + resetState(State.Pressed); + Log.d("Button state: ", state); + return true; + } + return false; + } + override void onDraw(DrawBuf buf) { if (visibility != Visibility.Visible) return; diff --git a/src/dlangui/widgets/styles.d b/src/dlangui/widgets/styles.d index 40d3e65d..dd455fb1 100644 --- a/src/dlangui/widgets/styles.d +++ b/src/dlangui/widgets/styles.d @@ -20,6 +20,13 @@ immutable int FILL_PARENT = int.max - 1; immutable int WRAP_CONTENT = int.max - 2; immutable int WEIGHT_UNSPECIFIED = -1; +enum State : uint { + Normal = 0, + Pressed = 1, + Focused = 2, + Disabled = 4, +} + enum Align : ubyte { Unspecified = ALIGN_UNSPECIFIED, Left = 1, @@ -38,8 +45,8 @@ class Style { protected Theme _theme; protected Style _parentStyle; protected string _parentId; - protected ubyte _stateMask; - protected ubyte _stateValue; + protected uint _stateMask; + protected uint _stateValue; protected ubyte _align = Align.TopLeft; protected ubyte _fontStyle = FONT_STYLE_UNSPECIFIED; protected FontFamily _fontFamily = FontFamily.Unspecified; @@ -77,7 +84,7 @@ class Style { return currentTheme; } - @property string id() { return _id; } + @property string id() const { return _id; } @property const(Style) parentStyle() const { if (_parentStyle !is null) @@ -394,7 +401,7 @@ class Style { } /// create state substyle for this style - Style createState(ubyte stateMask = 0, ubyte stateValue = 0) { + Style createState(uint stateMask = 0, uint stateValue = 0) { Style child = createSubstyle(id); child._stateMask = stateMask; child._stateValue = stateValue; @@ -404,7 +411,7 @@ class Style { } /// find substyle based on widget state (e.g. focused, pressed, ...) - Style forState(ubyte state) { + const(Style) forState(uint state) const { if (state == 0) return this; if (id is null && parentStyle !is null && _substates.length == 0) diff --git a/src/dlangui/widgets/widget.d b/src/dlangui/widgets/widget.d index 2126dc42..97d97645 100644 --- a/src/dlangui/widgets/widget.d +++ b/src/dlangui/widgets/widget.d @@ -1,6 +1,7 @@ module dlangui.widgets.widget; public import dlangui.core.types; +public import dlangui.core.events; public import dlangui.widgets.styles; public import dlangui.graphics.drawbuf; public import dlangui.graphics.images; @@ -32,6 +33,10 @@ class Widget { protected string _styleId; /// own copy of style - to override some of style properties, null of no properties overriden protected Style _ownStyle; + + /// widget state (set of flags from State enum) + protected uint _state; + /// width measured by measure() protected int _measuredWidth; /// height measured by measure() @@ -55,6 +60,28 @@ class Widget { return _ownStyle; return currentTheme.get(_styleId); } + /// accessor to style - by lookup in theme by styleId (if style id is not set, theme base style will be used). + protected @property const (Style) style(uint stateFlags) const { + const (Style) normalStyle = style(); + if (!stateFlags) // state is normal + return normalStyle; + const (Style) stateStyle = normalStyle.forState(stateFlags); + if (stateStyle !is normalStyle) + return stateStyle; // found style for state in current style + // lookup state style in parent (one level max) + const (Style) parentStyle = normalStyle.parentStyle; + if (parentStyle is normalStyle) + return normalStyle; // no parent + const (Style) parentStateStyle = parentStyle.forState(stateFlags); + if (parentStateStyle !is parentStyle) + return parentStateStyle; // found style for state in parent + return normalStyle; // fallback to current style + } + /// returns style for current widget state + protected @property const(Style) stateStyle() const { + return style(state); + } + /// enforces widget's own style - allows override some of style properties protected @property Style ownStyle() { if (_ownStyle is null) @@ -67,7 +94,30 @@ class Widget { /// set widget id @property void id(string id) { _id = id; } /// compare widget id with specified value, returs true if matches - bool compareId(string id) { return (_id !is null) && id.equal(_id); } + bool compareId(string id) const { return (_id !is null) && id.equal(_id); } + + /// widget state (set of flags from State enum) + @property uint state() const { + return _state; + } + /// set new widget state (set of flags from State enum) + @property Widget state(uint newState) { + if (newState != _state) { + _state = newState; + // need to redraw + invalidate(); + } + return this; + } + /// add state flags (set of flags from State enum) + @property Widget setState(uint stateFlagsToSet) { + return state(state | stateFlagsToSet); + } + /// remove state flags (set of flags from State enum) + @property Widget resetState(uint stateFlagsToUnset) { + return state(state & ~stateFlagsToUnset); + } + //====================================================== // Style related properties @@ -101,7 +151,7 @@ class Widget { /// set padding for widget - override one from style @property Widget padding(Rect rc) { ownStyle.padding = rc; return this; } /// returns background color - @property uint backgroundColor() const { return style.backgroundColor; } + @property uint backgroundColor() const { return stateStyle.backgroundColor; } /// set background color for widget - override one from style @property Widget backgroundColor(uint color) { ownStyle.backgroundColor = color; return this; } /// get text color (ARGB 32 bit value) @@ -109,23 +159,23 @@ class Widget { /// set text color (ARGB 32 bit value) @property Widget textColor(uint value) { ownStyle.textColor = value; return this; } /// returns font face - @property string fontFace() const { return style.fontFace; } + @property string fontFace() const { return stateStyle.fontFace; } /// set font face for widget - override one from style @property Widget fontFace(string face) { ownStyle.fontFace = face; return this; } /// returns font style (italic/normal) - @property bool fontItalic() const { return style.fontItalic; } + @property bool fontItalic() const { return stateStyle.fontItalic; } /// set font style (italic/normal) for widget - override one from style @property Widget fontItalic(bool italic) { ownStyle.fontStyle = italic ? FONT_STYLE_ITALIC : FONT_STYLE_NORMAL; return this; } /// returns font weight - @property ushort fontWeight() const { return style.fontWeight; } + @property ushort fontWeight() const { return stateStyle.fontWeight; } /// set font weight for widget - override one from style @property Widget fontWeight(ushort weight) { ownStyle.fontWeight = weight; return this; } /// returns font size in pixels - @property ushort fontSize() const { return style.fontSize; } + @property ushort fontSize() const { return stateStyle.fontSize; } /// set font size for widget - override one from style @property Widget fontSize(ushort size) { ownStyle.fontSize = size; return this; } /// returns font family - @property FontFamily fontFamily() const { return style.fontFamily; } + @property FontFamily fontFamily() const { return stateStyle.fontFamily; } /// set font family for widget - override one from style @property Widget fontFamily(FontFamily family) { ownStyle.fontFamily = family; return this; } /// returns alignment (combined vertical and horizontal) @@ -137,7 +187,7 @@ class Widget { /// returns vertical alignment @property Align halign() { return cast(Align)(alignment & Align.HCenter); } /// returns font set for widget using style or set manually - @property FontRef font() const { return style.font; } + @property FontRef font() const { return stateStyle.font; } /// returns widget content text (override to support this) @property dstring text() { return ""; } @@ -200,6 +250,22 @@ class Widget { return this; } + /// returns true if point is inside of this widget + bool isPointInside(int x, int y) { + return _pos.isPointInside(x, y); + } + + // ======================================================= + // Events + + /// process mouse event; return true if event is processed by widget. + bool onMouseEvent(MouseEvent event) { + return false; + } + + // ======================================================= + // Layout and measurement methods + /// request relayout of widget and its children void requestLayout() { _needLayout = true; @@ -323,19 +389,49 @@ class Widget { /// returns index of widget in child list, -1 if passed widget is not a child of this widget int childIndex(Widget item) { return -1; } + + /// returns true if item is child of this widget (when deepSearch == true - returns true if item is this widget or one of children inside children tree). + bool isChild(Widget item, bool deepSearch = true) { + if (deepSearch) { + // this widget or some widget inside children tree + if (item is this) + return true; + for (int i = 0; i < childCount; i++) { + if (child(i).isChild(item)) + return true; + } + } else { + // only one of children + for (int i = 0; i < childCount; i++) { + if (item is child(i)) + return true; + } + } + return false; + } + /// find child by id, returns null if not found - Widget childById(string id) { - if (compareId(id)) - return this; - // lookup children - for (int i = childCount - 1; i >= 0; i--) { - Widget res = child(i).childById(id); - if (res !is null) - return res; + Widget childById(string id, bool deepSearch = true) { + if (deepSearch) { + // search everywhere inside child tree + if (compareId(id)) + return this; + // lookup children + for (int i = childCount - 1; i >= 0; i--) { + Widget res = child(i).childById(id); + if (res !is null) + return res; + } + } else { + // search only across children of this widget + for (int i = childCount - 1; i >= 0; i--) + if (id.equal(child(i).id)) + return child(i); } // not found return null; } + /// returns parent widget, null for top level widget @property Widget parent() { return _parent; } /// sets parent for widget