mouse event processing

This commit is contained in:
Vadim Lopatin 2014-03-17 12:18:53 +04:00
parent 8df36ce12a
commit 08a95002c1
7 changed files with 436 additions and 148 deletions

View File

@ -2,17 +2,14 @@ module dlangui.core.events;
import std.conv; import std.conv;
enum MouseAction : ushort { enum MouseAction : ubyte {
LButtonDown, Cancel,
LButtonUp, ButtonDown, // button is down
MButtonDown, ButtonUp, // button is up
MButtonUp, Move, // mouse pointer is moving
RButtonDown,
RButtonUp,
Wheel, Wheel,
Move, FocusIn,
Leave, FocusOut
Hover
} }
enum MouseFlag : ushort { enum MouseFlag : ushort {
@ -26,9 +23,11 @@ enum MouseFlag : ushort {
} }
/// mouse button state details /// mouse button state details
struct ButtondDetails { struct ButtonDetails {
/// Clock.currStdTime() for down event of this button (0 if button is up). /// Clock.currStdTime() for down event of this button (0 if button is up).
long _downTs; long _downTs;
/// Clock.currStdTime() for up event of this button (0 if button is still down).
long _upTs;
/// x coordinates of down event /// x coordinates of down event
short _downX; short _downX;
/// y coordinates of down event /// y coordinates of down event
@ -40,32 +39,59 @@ struct ButtondDetails {
_downX = x; _downX = x;
_downY = y; _downY = y;
_downFlags = flags; _downFlags = flags;
_upTs = 0;
_downTs = std.datetime.Clock.currStdTime; _downTs = std.datetime.Clock.currStdTime;
} }
/// update for button up /// update for button up
void up() { void up(short x, short y, ushort flags) {
_downTs = 0; _upTs = std.datetime.Clock.currStdTime;
_downX = 0;
_downY = 0;
_downFlags = 0;
} }
@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 { class MouseEvent {
protected long _eventTimestamp;
protected MouseAction _action; protected MouseAction _action;
protected ushort _flags; protected MouseButton _button;
protected short _x; protected short _x;
protected short _y; protected short _y;
protected ButonDetails _lbutton; protected ushort _flags;
protected ButonDetails _mbutton; protected ButtonDetails _lbutton;
protected ButonDetails _rbutton; 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 MouseAction action() { return _action; }
@property ushort flags() { return _flags; } @property ushort flags() { return _flags; }
@property short x() { return _x; } @property short x() { return _x; }
@property short y() { return _y; } @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; _action = a;
_button = b;
_flags = f; _flags = f;
_x = x; _x = x;
_y = y; _y = y;

View File

@ -53,6 +53,14 @@ struct Rect {
return false; return false;
return true; 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 /// character glyph

View File

@ -7,9 +7,9 @@ import std.file;
private import dlangui.graphics.gldrawbuf; private import dlangui.graphics.gldrawbuf;
class Window { class Window {
int _dx; protected int _dx;
int _dy; protected int _dy;
Widget _mainWidget; protected Widget _mainWidget;
@property int width() { return _dx; } @property int width() { return _dx; }
@property int height() { return _dy; } @property int height() { return _dy; }
@property Widget mainWidget() { return _mainWidget; } @property Widget mainWidget() { return _mainWidget; }

View File

@ -13,6 +13,7 @@ import dlangui.platforms.common.platform;
import dlangui.platforms.windows.win32fonts; import dlangui.platforms.windows.win32fonts;
import dlangui.platforms.windows.win32drawbuf; import dlangui.platforms.windows.win32drawbuf;
import dlangui.widgets.styles; import dlangui.widgets.styles;
import dlangui.widgets.widget;
import dlangui.graphics.drawbuf; import dlangui.graphics.drawbuf;
import dlangui.graphics.images; import dlangui.graphics.images;
import dlangui.graphics.fonts; import dlangui.graphics.fonts;
@ -127,14 +128,17 @@ version (USE_OPENGL) {
} }
class Win32Window : Window { class Win32Window : Window {
private HWND _hwnd; Win32Platform _platform;
HGLRC _hGLRC; // opengl context HWND _hwnd;
HPALETTE _hPalette; version (USE_OPENGL) {
HGLRC _hGLRC; // opengl context
HPALETTE _hPalette;
}
string _caption; string _caption;
Win32ColorDrawBuf _drawbuf; Win32ColorDrawBuf _drawbuf;
bool useOpengl; bool useOpengl;
this(string windowCaption, Window parent) { this(Win32Platform platform, string windowCaption, Window parent) {
import derelict.opengl3.wgl; _platform = platform;
_caption = windowCaption; _caption = windowCaption;
_hwnd = CreateWindow(toUTF16z(WIN_CLASS_NAME), // window class name _hwnd = CreateWindow(toUTF16z(WIN_CLASS_NAME), // window class name
toUTF16z(windowCaption), // window caption toUTF16z(windowCaption), // window caption
@ -149,6 +153,7 @@ class Win32Window : Window {
cast(void*)this); // creation parameters cast(void*)this); // creation parameters
version (USE_OPENGL) { version (USE_OPENGL) {
import derelict.opengl3.wgl;
/* initialize OpenGL rendering */ /* initialize OpenGL rendering */
HDC hDC = GetDC(_hwnd); 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) { version (USE_OPENGL) {
private void paintUsingOpenGL() { 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() { void onPaint() {
Log.d("onPaint()"); Log.d("onPaint()");
version (USE_OPENGL) { version (USE_OPENGL) {
@ -306,18 +314,115 @@ class Win32Window : Window {
} }
} }
protected ButonDetails _lbutton; protected ButtonDetails _lbutton;
protected ButonDetails _mbutton; protected ButtonDetails _mbutton;
protected ButonDetails _rbutton; protected ButtonDetails _rbutton;
override bool onMouseEvent(MouseEvent event) { override bool onMouseEvent(MouseEvent event) {
return false; return false;
} }
bool onMouse(MouseEvent event) { protected bool dispatchMouseEvent(Widget root, MouseEvent event) {
Log.d("MouseEvent ", event.action, " flags=", event.flags, " x=", event.x, " y=", event.y); // only route mouse events to visible widgets
if (event.action == MouseAction.LButtonDown) if (root.visibility != Visibility.Visible)
return true; 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; 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) { 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; return res;
} }
private __gshared Win32Platform platform;
int myWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int iCmdShow) int myWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int iCmdShow)
{ {
setFileLogger(std.stdio.File("ui.log", "w")); setFileLogger(std.stdio.File("ui.log", "w"));
@ -421,7 +552,7 @@ int myWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int
_cmdShow = iCmdShow; _cmdShow = iCmdShow;
_hInstance = hInstance; _hInstance = hInstance;
Win32Platform platform = new Win32Platform(); platform = new Win32Platform();
if (!platform.registerWndClass()) { if (!platform.registerWndClass()) {
MessageBoxA(null, "This program requires Windows NT!", "DLANGUI App".toStringz, MB_ICONERROR); MessageBoxA(null, "This program requires Windows NT!", "DLANGUI App".toStringz, MB_ICONERROR);
return 0; return 0;
@ -499,8 +630,16 @@ LRESULT WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{ {
HDC hdc; HDC hdc;
RECT rect; RECT rect;
void * p = cast(void*)GetWindowLongPtr(hwnd, GWLP_USERDATA); 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) switch (message)
{ {
case WM_CREATE: case WM_CREATE:
@ -509,23 +648,31 @@ LRESULT WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
window = cast(Win32Window)pcreateStruct.lpCreateParams; window = cast(Win32Window)pcreateStruct.lpCreateParams;
void * ptr = cast(void*) window; void * ptr = cast(void*) window;
SetWindowLongPtr(hwnd, GWLP_USERDATA, cast(LONG_PTR)ptr); SetWindowLongPtr(hwnd, GWLP_USERDATA, cast(LONG_PTR)ptr);
window._hwnd = hwnd;
window.onCreate(); 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; return 0;
case WM_WINDOWPOSCHANGED: case WM_WINDOWPOSCHANGED:
{ {
WINDOWPOS * pos = cast(WINDOWPOS*)lParam; if (window !is null) {
GetClientRect(hwnd, &rect); WINDOWPOS * pos = cast(WINDOWPOS*)lParam;
int dx = rect.right - rect.left; GetClientRect(hwnd, &rect);
int dy = rect.bottom - rect.top; int dx = rect.right - rect.left;
//window.onResize(pos.cx, pos.cy); int dy = rect.bottom - rect.top;
window.onResize(dx, dy); //window.onResize(pos.cx, pos.cy);
InvalidateRect(hwnd, null, FALSE); window.onResize(dx, dy);
//UpdateWindow(hwnd); InvalidateRect(hwnd, null, FALSE);
}
} }
return 0; return 0;
case WM_ERASEBKGND: case WM_ERASEBKGND:
// processed
return 1; return 1;
case WM_PAINT: case WM_PAINT:
{ {
@ -534,39 +681,21 @@ LRESULT WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
} }
return 0; // processed return 0; // processed
case WM_MOUSEMOVE: 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: 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: 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: 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: 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: 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: case WM_RBUTTONUP:
if (window !is null) 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 return 0; // processed
case WM_DESTROY: case WM_GETMINMAXINFO:
window.onDestroy(); case WM_NCCREATE:
PostQuitMessage(0); case WM_NCCALCSIZE:
return 0;
default: default:
//Log.d("Unhandled message ", message);
break;
} }
return DefWindowProc(hwnd, message, wParam, lParam); return DefWindowProc(hwnd, message, wParam, lParam);

View File

@ -17,11 +17,33 @@ class TextWidget : Widget {
requestLayout(); requestLayout();
return this; return this;
} }
override void measure(int parentWidth, int parentHeight) { override void measure(int parentWidth, int parentHeight) {
FontRef font = font(); FontRef font = font();
Point sz = font.textSize(text); Point sz = font.textSize(text);
measuredContent(parentWidth, parentHeight, sz.x, sz.y); 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) { override void onDraw(DrawBuf buf) {
if (visibility != Visibility.Visible) if (visibility != Visibility.Visible)
return; return;

View File

@ -20,6 +20,13 @@ immutable int FILL_PARENT = int.max - 1;
immutable int WRAP_CONTENT = int.max - 2; immutable int WRAP_CONTENT = int.max - 2;
immutable int WEIGHT_UNSPECIFIED = -1; immutable int WEIGHT_UNSPECIFIED = -1;
enum State : uint {
Normal = 0,
Pressed = 1,
Focused = 2,
Disabled = 4,
}
enum Align : ubyte { enum Align : ubyte {
Unspecified = ALIGN_UNSPECIFIED, Unspecified = ALIGN_UNSPECIFIED,
Left = 1, Left = 1,
@ -38,8 +45,8 @@ class Style {
protected Theme _theme; protected Theme _theme;
protected Style _parentStyle; protected Style _parentStyle;
protected string _parentId; protected string _parentId;
protected ubyte _stateMask; protected uint _stateMask;
protected ubyte _stateValue; protected uint _stateValue;
protected ubyte _align = Align.TopLeft; protected ubyte _align = Align.TopLeft;
protected ubyte _fontStyle = FONT_STYLE_UNSPECIFIED; protected ubyte _fontStyle = FONT_STYLE_UNSPECIFIED;
protected FontFamily _fontFamily = FontFamily.Unspecified; protected FontFamily _fontFamily = FontFamily.Unspecified;
@ -77,7 +84,7 @@ class Style {
return currentTheme; return currentTheme;
} }
@property string id() { return _id; } @property string id() const { return _id; }
@property const(Style) parentStyle() const { @property const(Style) parentStyle() const {
if (_parentStyle !is null) if (_parentStyle !is null)
@ -394,7 +401,7 @@ class Style {
} }
/// create state substyle for this 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); Style child = createSubstyle(id);
child._stateMask = stateMask; child._stateMask = stateMask;
child._stateValue = stateValue; child._stateValue = stateValue;
@ -404,7 +411,7 @@ class Style {
} }
/// find substyle based on widget state (e.g. focused, pressed, ...) /// find substyle based on widget state (e.g. focused, pressed, ...)
Style forState(ubyte state) { const(Style) forState(uint state) const {
if (state == 0) if (state == 0)
return this; return this;
if (id is null && parentStyle !is null && _substates.length == 0) if (id is null && parentStyle !is null && _substates.length == 0)

View File

@ -1,6 +1,7 @@
module dlangui.widgets.widget; module dlangui.widgets.widget;
public import dlangui.core.types; public import dlangui.core.types;
public import dlangui.core.events;
public import dlangui.widgets.styles; public import dlangui.widgets.styles;
public import dlangui.graphics.drawbuf; public import dlangui.graphics.drawbuf;
public import dlangui.graphics.images; public import dlangui.graphics.images;
@ -32,6 +33,10 @@ class Widget {
protected string _styleId; protected string _styleId;
/// own copy of style - to override some of style properties, null of no properties overriden /// own copy of style - to override some of style properties, null of no properties overriden
protected Style _ownStyle; protected Style _ownStyle;
/// widget state (set of flags from State enum)
protected uint _state;
/// width measured by measure() /// width measured by measure()
protected int _measuredWidth; protected int _measuredWidth;
/// height measured by measure() /// height measured by measure()
@ -55,6 +60,28 @@ class Widget {
return _ownStyle; return _ownStyle;
return currentTheme.get(_styleId); 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 /// enforces widget's own style - allows override some of style properties
protected @property Style ownStyle() { protected @property Style ownStyle() {
if (_ownStyle is null) if (_ownStyle is null)
@ -67,7 +94,30 @@ class Widget {
/// set widget id /// set widget id
@property void id(string id) { _id = id; } @property void id(string id) { _id = id; }
/// compare widget id with specified value, returs true if matches /// 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 // Style related properties
@ -101,7 +151,7 @@ class Widget {
/// set padding for widget - override one from style /// set padding for widget - override one from style
@property Widget padding(Rect rc) { ownStyle.padding = rc; return this; } @property Widget padding(Rect rc) { ownStyle.padding = rc; return this; }
/// returns background color /// 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 /// set background color for widget - override one from style
@property Widget backgroundColor(uint color) { ownStyle.backgroundColor = color; return this; } @property Widget backgroundColor(uint color) { ownStyle.backgroundColor = color; return this; }
/// get text color (ARGB 32 bit value) /// get text color (ARGB 32 bit value)
@ -109,23 +159,23 @@ class Widget {
/// set text color (ARGB 32 bit value) /// set text color (ARGB 32 bit value)
@property Widget textColor(uint value) { ownStyle.textColor = value; return this; } @property Widget textColor(uint value) { ownStyle.textColor = value; return this; }
/// returns font face /// 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 /// set font face for widget - override one from style
@property Widget fontFace(string face) { ownStyle.fontFace = face; return this; } @property Widget fontFace(string face) { ownStyle.fontFace = face; return this; }
/// returns font style (italic/normal) /// 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 /// 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; } @property Widget fontItalic(bool italic) { ownStyle.fontStyle = italic ? FONT_STYLE_ITALIC : FONT_STYLE_NORMAL; return this; }
/// returns font weight /// 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 /// set font weight for widget - override one from style
@property Widget fontWeight(ushort weight) { ownStyle.fontWeight = weight; return this; } @property Widget fontWeight(ushort weight) { ownStyle.fontWeight = weight; return this; }
/// returns font size in pixels /// 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 /// set font size for widget - override one from style
@property Widget fontSize(ushort size) { ownStyle.fontSize = size; return this; } @property Widget fontSize(ushort size) { ownStyle.fontSize = size; return this; }
/// returns font family /// 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 /// set font family for widget - override one from style
@property Widget fontFamily(FontFamily family) { ownStyle.fontFamily = family; return this; } @property Widget fontFamily(FontFamily family) { ownStyle.fontFamily = family; return this; }
/// returns alignment (combined vertical and horizontal) /// returns alignment (combined vertical and horizontal)
@ -137,7 +187,7 @@ class Widget {
/// returns vertical alignment /// returns vertical alignment
@property Align halign() { return cast(Align)(alignment & Align.HCenter); } @property Align halign() { return cast(Align)(alignment & Align.HCenter); }
/// returns font set for widget using style or set manually /// 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) /// returns widget content text (override to support this)
@property dstring text() { return ""; } @property dstring text() { return ""; }
@ -200,6 +250,22 @@ class Widget {
return this; 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 /// request relayout of widget and its children
void requestLayout() { void requestLayout() {
_needLayout = true; _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 /// returns index of widget in child list, -1 if passed widget is not a child of this widget
int childIndex(Widget item) { return -1; } 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 /// find child by id, returns null if not found
Widget childById(string id) { Widget childById(string id, bool deepSearch = true) {
if (compareId(id)) if (deepSearch) {
return this; // search everywhere inside child tree
// lookup children if (compareId(id))
for (int i = childCount - 1; i >= 0; i--) { return this;
Widget res = child(i).childById(id); // lookup children
if (res !is null) for (int i = childCount - 1; i >= 0; i--) {
return res; 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 // not found
return null; return null;
} }
/// returns parent widget, null for top level widget /// returns parent widget, null for top level widget
@property Widget parent() { return _parent; } @property Widget parent() { return _parent; }
/// sets parent for widget /// sets parent for widget