diff --git a/src/dlangui/widgets/styles.d b/src/dlangui/widgets/styles.d index 3844d612..1c5df7cd 100644 --- a/src/dlangui/widgets/styles.d +++ b/src/dlangui/widgets/styles.d @@ -13,6 +13,8 @@ immutable ushort FONT_WEIGHT_UNSPECIFIED = 0x0000; immutable ubyte FONT_STYLE_UNSPECIFIED = 0xFF; immutable ubyte FONT_STYLE_NORMAL = 0x00; immutable ubyte FONT_STYLE_ITALIC = 0x01; +/// use as widget.layout() param to avoid applying of parent size +immutable int SIZE_UNSPECIFIED = int.max; enum Align : ubyte { Unspecified = ALIGN_UNSPECIFIED, @@ -45,6 +47,10 @@ class Style { protected string _backgroundImageId; protected Rect _padding; protected Rect _margins; + protected int _minWidth = SIZE_UNSPECIFIED; + protected int _maxWidth = SIZE_UNSPECIFIED; + protected int _minHeight = SIZE_UNSPECIFIED; + protected int _maxHeight = SIZE_UNSPECIFIED; protected Style[] _substates; protected Style[] _children; @@ -194,6 +200,59 @@ class Style { return parentStyle.backgroundImageId; } + //=================================================== + // size restrictions + + /// minimal width constraint, 0 if limit is not set + @property uint minWidth() const { + if (_minWidth != SIZE_UNSPECIFIED) + return _minWidth; + else + return parentStyle.minWidth; + } + /// max width constraint, returns SIZE_UNSPECIFIED if limit is not set + @property uint maxWidth() const { + if (_maxWidth != SIZE_UNSPECIFIED) + return _maxWidth; + else + return parentStyle.maxWidth; + } + /// minimal height constraint, 0 if limit is not set + @property uint minHeight() const { + if (_minHeight != SIZE_UNSPECIFIED) + return _minHeight; + else + return parentStyle.minHeight; + } + /// max height constraint, SIZE_UNSPECIFIED if limit is not set + @property uint maxHeight() const { + if (_maxHeight != SIZE_UNSPECIFIED) + return _maxHeight; + else + return parentStyle.maxHeight; + } + /// set min width constraint + @property Style minWidth(int value) { + _minWidth = value; + return this; + } + /// set max width constraint + @property Style maxWidth(int value) { + _maxWidth = value; + return this; + } + /// set min height constraint + @property Style minHeight(int value) { + _minHeight = value; + return this; + } + /// set max height constraint + @property Style maxHeight(int value) { + _maxHeight = value; + return this; + } + + //=================================================== // alignment @@ -326,6 +385,8 @@ class Theme : Style { _fontWeight = 400; _fontFace = "Arial"; // TODO: from settings _fontFamily = FontFamily.SansSerif; + _minHeight = 0; + _minWidth = 0; } /// create wrapper style which will have currentTheme.get(id) as parent instead of fixed parent - to modify some base style properties in widget diff --git a/src/dlangui/widgets/widget.d b/src/dlangui/widgets/widget.d index cb3b4d46..849a19e9 100644 --- a/src/dlangui/widgets/widget.d +++ b/src/dlangui/widgets/widget.d @@ -5,11 +5,29 @@ public import dlangui.widgets.styles; public import dlangui.graphics.drawbuf; public import dlangui.graphics.images; public import dlangui.graphics.fonts; + import dlangui.platforms.common.platform; +import std.algorithm; + + +/// Visibility (see Android View Visibility) +enum Visibility : ubyte { + /// Visible on screen (default) + Visible, + /// Not visible, but occupies a space in layout + Invisible, + /// Completely hidden, as not has been added + Gone +} + class Widget { + /// widget id + protected string _id; /// current widget position, set by layout() protected Rect _pos; + /// widget visibility: either Visible, Invisible, Gone + protected Visibility _visibility = Visibility.Visible; // visible by default /// style id to lookup style in theme protected string _styleId; /// own copy of style - to override some of style properties, null of no properties overriden @@ -45,13 +63,20 @@ class Widget { return _ownStyle; } + /// returns widget id, null if not set + @property string id() const { return _styleId; } + /// 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); } + //====================================================== // Style related properties /// returns widget style id, null if not set - @property void styleId(string id) { _styleId = id; } - /// set widget style id @property string styleId() const { return _styleId; } + /// set widget style id + @property void styleId(string id) { _styleId = id; } /// get margins (between widget bounds and its background) @property Rect margins() const { return style.margins; } /// set margins for widget - override one from style @@ -119,12 +144,54 @@ class Widget { @property int width() { return _pos.width; } /// returns current height of widget in pixels @property int height() { return _pos.height; } - /// Measure widget according to desired width and height constraints. (Step 1 of two phase layout). - void measure(int width, int height) { - _measuredWidth = _measuredHeight = 0; + + /// returns widget visibility (Visible, Invisible, Gone) + @property Visibility visibility() { return _visibility; } + /// sets widget visibility (Visible, Invisible, Gone) + @property Widget visibility(Visibility visible) { + _visibility = visible; + requestLayout(); + return this; } + + /// request relayout of widget and its children + void requestLayout() { + _needLayout = true; + } + /// request redraw + void invalidate() { + _needDraw = true; + } + + /// Measure widget according to desired width and height constraints. (Step 1 of two phase layout). + void measure(int parentWidth, int parentHeight) { + measuredContent(parentWidth, parentHeight, 0, 0); + } + + /// helper function for implement measure() when widget's content dimensions are known + protected void measuredContent(int parentWidth, int parentHeight, int contentWidth, int contentHeight) { + if (visibility == Visibility.Gone) { + _measuredWidth = _measuredHeight = 0; + return; + } + Rect m = margins; + Rect p = padding; + int dx = m.left + m.right + p.left + p.right + contentWidth; + int dy = m.top + m.bottom + p.top + p.bottom + contentHeight; + // check for margins and padding + if (parentWidth != SIZE_UNSPECIFIED && dx > parentWidth) + dx = parentWidth; + if (parentHeight != SIZE_UNSPECIFIED && dy > parentHeight) + dy = parentHeight; + _measuredWidth = dx; + _measuredHeight = dy; + } + /// Set widget rectangle to specified value and layout widget contents. (Step 2 of two phase layout). void layout(Rect rc) { + if (visibility == Visibility.Gone) { + return; + } _pos = rc; _needLayout = false; } @@ -146,6 +213,8 @@ class Widget { } /// Draw widget at its position to buffer void onDraw(DrawBuf buf) { + if (visibility != Visibility.Visible) + return; Rect rc = _pos; applyMargins(rc); buf.fillRect(_pos, backgroundColor); @@ -198,6 +267,19 @@ class Widget { @property int childCount() { return 0; } /// returns child by index Widget child(int index) { return null; } + /// 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; + } + // not found + return null; + } /// returns parent widget, null for top level widget @property Widget parent() { return _parent; } /// sets parent for widget