From 1341bb6e718937455bca23d10b7ebf480bbfb71c Mon Sep 17 00:00:00 2001 From: Vadim Lopatin Date: Sat, 8 Mar 2014 22:09:35 +0400 Subject: [PATCH] fix linear layout implementation --- examples/example1/winmain.d | 71 +++++++------ src/dlangui/all.d | 4 +- src/dlangui/platforms/common/platform.d | 134 ++++++++++++------------ src/dlangui/widgets/controls.d | 31 +++--- src/dlangui/widgets/layouts.d | 66 ++++++++---- src/dlangui/widgets/styles.d | 27 ++++- src/dlangui/widgets/widget.d | 26 +++-- 7 files changed, 218 insertions(+), 141 deletions(-) diff --git a/examples/example1/winmain.d b/examples/example1/winmain.d index a5cdd6f1..d93f1c0c 100644 --- a/examples/example1/winmain.d +++ b/examples/example1/winmain.d @@ -1,12 +1,12 @@ -module winmain; - -import dlangui.all; -import std.stdio; - -/// workaround for link issue when WinMain is located in library -version(Windows) { - import win32.windows; - import dlangui.platforms.windows.winapp; +module winmain; + +import dlangui.all; +import std.stdio; + +/// workaround for link issue when WinMain is located in library +version(Windows) { + import win32.windows; + import dlangui.platforms.windows.winapp; extern (Windows) int WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) @@ -15,24 +15,35 @@ version(Windows) { lpCmdLine, nCmdShow); } } - -/// entry point for dlangui based application -extern (C) int UIAppMain(string[] args) { - // setup resource dir - string resourceDir = exePath() ~ "..\\res\\"; - string[] imageDirs = [ - resourceDir - ]; - drawableCache.resourcePaths = imageDirs; - - // create window - Window window = Platform.instance().createWindow("My Window", null); - Widget myWidget = (new Button()).textColor(0x40FF4000); - myWidget.text = "Some strange text string. 1234567890"; - window.mainWidget = myWidget; - window.show(); - window.windowCaption = "New Window Caption"; - - // run message loop - return Platform.instance().enterMessageLoop(); -} + +/// entry point for dlangui based application +extern (C) int UIAppMain(string[] args) { + // setup resource dir + string resourceDir = exePath() ~ "..\\res\\"; + string[] imageDirs = [ + resourceDir + ]; + drawableCache.resourcePaths = imageDirs; + + // create window + Window window = Platform.instance().createWindow("My Window", null); + LinearLayout layout = new LinearLayout(); + layout.addChild((new TextWidget()).textColor(0x00802000).text("Text widget 0")); + layout.addChild((new TextWidget()).textColor(0x40FF4000).text("Text widget")); + layout.addChild((new Button()).textColor(0x40FF4000).text("Button1")); + layout.addChild((new Button()).textColor(0x000000FF).text("Button2")); + layout.addChild((new TextWidget()).textColor(0x40FF4000).text("Text widget")); + layout.addChild((new ImageWidget()).drawableId("exit").padding(Rect(5,5,5,5))); + layout.addChild((new TextWidget()).textColor(0xFF4000).text("Text widget2").padding(Rect(5,5,5,5)).margins(Rect(5,5,5,5)).backgroundColor(0xA0A0A0)); + layout.addChild((new Button()).textColor(0x000000FF).text("Button3").layoutHeight(FILL_PARENT)); + layout.addChild((new TextWidget()).textColor(0x004000).text("Text widget3 with very long text")); + + layout.layoutHeight(FILL_PARENT).layoutWidth(FILL_PARENT); + + window.mainWidget = layout; + window.show(); + window.windowCaption = "New Window Caption"; + + // run message loop + return Platform.instance().enterMessageLoop(); +} diff --git a/src/dlangui/all.d b/src/dlangui/all.d index 413e62d3..78d5ebff 100644 --- a/src/dlangui/all.d +++ b/src/dlangui/all.d @@ -1,8 +1,10 @@ module dlangui.all; +public import dlangui.core.logger; +public import dlangui.core.types; public import dlangui.platforms.common.platform; public import dlangui.graphics.images; public import dlangui.widgets.widget; public import dlangui.widgets.controls; -public import dlangui.core.logger; +public import dlangui.widgets.layouts; public import dlangui.graphics.fonts; diff --git a/src/dlangui/platforms/common/platform.d b/src/dlangui/platforms/common/platform.d index 9b8865d7..d791c16e 100644 --- a/src/dlangui/platforms/common/platform.d +++ b/src/dlangui/platforms/common/platform.d @@ -1,68 +1,68 @@ -module dlangui.platforms.common.platform; - -import dlangui.widgets.widget; -import dlangui.graphics.drawbuf; -import std.file; - -public class Window { - int _dx; - int _dy; - Widget _mainWidget; - public @property int width() { return _dx; } - public @property int height() { return _dy; } - public @property Widget mainWidget() { return _mainWidget; } - public @property void mainWidget(Widget widget) { - if (_mainWidget !is null) - _mainWidget.window = null; - _mainWidget = widget; - if (_mainWidget !is null) - _mainWidget.window = this; - } - abstract public void show(); - abstract public @property string windowCaption(); - abstract public @property void windowCaption(string caption); - public void onResize(int width, int height) { - if (_dx == width && _dy == height) - return; - _dx = width; - _dy = height; - if (_mainWidget !is null) { - _mainWidget.measure(_dx, _dy); - _mainWidget.layout(Rect(0, 0, _dx, _dy)); - } - } - public void onDraw(DrawBuf buf) { - if (_mainWidget !is null) { - _mainWidget.onDraw(buf); - } - } -} - -public class Platform { - static __gshared Platform _instance; - public static void setInstance(Platform instance) { - _instance = instance; - } - public static Platform instance() { - return _instance; - } - abstract public Window createWindow(string windowCaption, Window parent); - abstract public int enterMessageLoop(); -} - -version (Windows) { - immutable char PATH_DELIMITER = '\\'; -} -version (Unix) { - immutable char PATH_DELIMITER = '/'; -} - +module dlangui.platforms.common.platform; + +import dlangui.widgets.widget; +import dlangui.graphics.drawbuf; +import std.file; + +public class Window { + int _dx; + int _dy; + Widget _mainWidget; + public @property int width() { return _dx; } + public @property int height() { return _dy; } + public @property Widget mainWidget() { return _mainWidget; } + public @property void mainWidget(Widget widget) { + if (_mainWidget !is null) + _mainWidget.window = null; + _mainWidget = widget; + if (_mainWidget !is null) + _mainWidget.window = this; + } + abstract public void show(); + abstract public @property string windowCaption(); + abstract public @property void windowCaption(string caption); + public void onResize(int width, int height) { + if (_dx == width && _dy == height) + return; + _dx = width; + _dy = height; + if (_mainWidget !is null) { + _mainWidget.measure(_dx, _dy); + _mainWidget.layout(Rect(0, 0, _dx, _dy)); + } + } + public void onDraw(DrawBuf buf) { + if (_mainWidget !is null) { + _mainWidget.onDraw(buf); + } + } +} + +public class Platform { + static __gshared Platform _instance; + public static void setInstance(Platform instance) { + _instance = instance; + } + public static Platform instance() { + return _instance; + } + abstract public Window createWindow(string windowCaption, Window parent); + abstract public int enterMessageLoop(); +} + +version (Windows) { + immutable char PATH_DELIMITER = '\\'; +} +version (Unix) { + immutable char PATH_DELIMITER = '/'; +} + /// returns current executable path only, including last path delimiter -string exePath() { - string path = thisExePath(); - int lastSlash = 0; - for (int i = 0; i < path.length; i++) - if (path[i] == PATH_DELIMITER) - lastSlash = i; - return path[0 .. lastSlash + 1]; -} +string exePath() { + string path = thisExePath(); + int lastSlash = 0; + for (int i = 0; i < path.length; i++) + if (path[i] == PATH_DELIMITER) + lastSlash = i; + return path[0 .. lastSlash + 1]; +} diff --git a/src/dlangui/widgets/controls.d b/src/dlangui/widgets/controls.d index 365e8028..6d026952 100644 --- a/src/dlangui/widgets/controls.d +++ b/src/dlangui/widgets/controls.d @@ -4,16 +4,20 @@ import dlangui.widgets.widget; /// static text widget class TextWidget : Widget { + this(string ID = null) { + super(ID); + styleId = "TEXT"; + } protected dstring _text; /// get widget text override @property dstring text() { return _text; } /// set text to show - override @property void text(dstring s) { + override @property Widget text(dstring s) { _text = s; requestLayout(); + return this; } override void measure(int parentWidth, int parentHeight) { - _measuredWidth = _measuredHeight = 0; FontRef font = font(); Point sz = font.textSize(text); measuredContent(parentWidth, parentHeight, sz.x, sz.y); @@ -24,8 +28,8 @@ class TextWidget : Widget { super.onDraw(buf); Rect rc = _pos; applyMargins(rc); - applyPadding(rc); ClipRectSaver(buf, rc); + applyPadding(rc); FontRef font = font(); Point sz = font.textSize(text); applyAlign(rc, sz); @@ -39,6 +43,10 @@ class ImageWidget : Widget { protected string _drawableId; protected DrawableRef _drawable; + this(string ID = null) { + super(ID); + } + /// get drawable image id @property string drawableId() { return _drawableId; } /// set drawable image id @@ -79,8 +87,8 @@ class ImageWidget : Widget { super.onDraw(buf); Rect rc = _pos; applyMargins(rc); - applyPadding(rc); ClipRectSaver(buf, rc); + applyPadding(rc); DrawableRef img = drawable; if (!img.isNull) { Point sz; @@ -95,16 +103,15 @@ class ImageWidget : Widget { class Button : Widget { protected dstring _text; override @property dstring text() { return _text; } - override @property void text(dstring s) { _text = s; } - this() { + override @property Widget text(dstring s) { _text = s; requestLayout(); return this; } + this(string ID = null) { + super(ID); styleId = "BUTTON"; } - override void measure(int width, int height) { - _measuredWidth = _measuredHeight = 0; - } - override void layout(Rect rc) { - _pos = rc; - _needLayout = false; + override void measure(int parentWidth, int parentHeight) { + FontRef font = font(); + Point sz = font.textSize(text); + measuredContent(parentWidth, parentHeight, sz.x, sz.y); } override void onDraw(DrawBuf buf) { super.onDraw(buf); diff --git a/src/dlangui/widgets/layouts.d b/src/dlangui/widgets/layouts.d index 1463209a..61f7c355 100644 --- a/src/dlangui/widgets/layouts.d +++ b/src/dlangui/widgets/layouts.d @@ -74,7 +74,6 @@ class LinearLayout : WidgetGroup { int contentHeight = 0; if (orientation == Orientation.Vertical) { // Vertical - int position = 0; int totalSize = 0; int delta = 0; int resizableSize = 0; @@ -89,16 +88,17 @@ class LinearLayout : WidgetGroup { if (item.visibility == Visibility.Gone) continue; visibleCount++; - totalSize += item.measuredHeight; + int weight = item.layoutWeight; + int size = item.measuredHeight; + totalSize += size; if (maxItem < item.measuredWidth) maxItem = item.measuredWidth; - int weight = item.layoutWeight; if (item.layoutHeight == FILL_PARENT) { resizableWeight += weight; - resizableSize += item.measuredHeight; + resizableSize += size * weight; } else { nonresizableWeight += weight; - nonresizableSize += item.measuredHeight; + nonresizableSize += size * weight; } } if (layoutWidth == WRAP_CONTENT && maxItem < rc.width) @@ -107,42 +107,59 @@ class LinearLayout : WidgetGroup { contentWidth = rc.width; if (layoutHeight == FILL_PARENT || totalSize > rc.height) delta = rc.height - totalSize; // total space to add to fit + // calculate resize options and scale bool needForceResize = false; bool needResize = false; int scaleFactor = 10000; // per weight unit if (delta != 0 && visibleCount > 0) { // need resize of some children needResize = true; - needForceResize = delta < 0 || resizableSize < delta; // do we need resize non-FILL_PARENT items? + // resize all if need to shrink or only resizable are too small to correct delta + needForceResize = delta < 0 || resizableWeight == 0; // || resizableSize * 2 / 3 < delta; // do we need resize non-FILL_PARENT items? + // calculate scale factor: weight / delta * 10000 if (needForceResize) - scaleFactor = 10000 * rc.height / (resizableSize + nonresizableSize) / (nonresizableWeight + resizableWeight); + scaleFactor = 10000 * delta / (nonresizableSize + resizableSize); else - scaleFactor = 10000 * rc.height / (rc.height - delta) / resizableWeight; + scaleFactor = 10000 * delta / resizableSize; } + //Log.d("VerticalLayout delta=", delta, ", nonres=", nonresizableWeight, ", res=", resizableWeight, ", scale=", scaleFactor); + // find last resized - to allow fill space 1 pixel accurate + Widget lastResized = null; for (int i = 0; i < _children.count; i++) { Widget item = _children.get(i); if (item.visibility == Visibility.Gone) continue; - int weight = item.layoutWeight; - if (item.layoutHeight == FILL_PARENT) { - resizableWeight += weight; - resizableSize += item.measuredHeight; - } else { - nonresizableWeight += weight; - nonresizableSize += item.measuredHeight; + if (item.layoutHeight == FILL_PARENT || needForceResize) { + lastResized = item; } - } + } + // final resize and layout of children + int position = 0; + int deltaTotal = 0; for (int i = 0; i < _children.count; i++) { Widget item = _children.get(i); if (item.visibility == Visibility.Gone) continue; int layoutSize = item.layoutHeight; - totalWeight += item.measuredHeight; int weight = item.layoutWeight; - if (layoutSize) { - resizableWeight += weight; - resizableSize += item.measuredHeight; + int size = item.measuredHeight; + if (needResize && (layoutSize == FILL_PARENT || needForceResize)) { + // do resize + int correction = scaleFactor * weight * size / 10000; + deltaTotal += correction; + // for last resized, apply additional correction to resolve calculation inaccuracy + if (item == lastResized) { + correction += delta - deltaTotal; + } + size += correction; } + // apply size + Rect childRect = rc; + childRect.top += position; + childRect.bottom = childRect.top + size; + childRect.right = childRect.left + contentWidth; + item.layout(childRect); + position += size; } } else { // Horizontal @@ -157,8 +174,13 @@ class LinearLayout : WidgetGroup { Rect rc = _pos; applyMargins(rc); applyPadding(rc); - // TODO - _needDraw = false; + ClipRectSaver(buf, rc); + for (int i = 0; i < _children.count; i++) { + Widget item = _children.get(i); + if (item.visibility != Visibility.Visible) + continue; + item.onDraw(buf); + } } } diff --git a/src/dlangui/widgets/styles.d b/src/dlangui/widgets/styles.d index b8a2b86e..40d3e65d 100644 --- a/src/dlangui/widgets/styles.d +++ b/src/dlangui/widgets/styles.d @@ -447,6 +447,29 @@ class Theme : Style { return style; } + // ================================================ + // override to avoid infinite recursion + /// font size + @property override string backgroundImageId() const { + return _backgroundImageId; + } + /// minimal width constraint, 0 if limit is not set + @property override uint minWidth() const { + return _minWidth; + } + /// max width constraint, returns SIZE_UNSPECIFIED if limit is not set + @property override uint maxWidth() const { + return _maxWidth; + } + /// minimal height constraint, 0 if limit is not set + @property override uint minHeight() const { + return _minHeight; + } + /// max height constraint, SIZE_UNSPECIFIED if limit is not set + @property override uint maxHeight() const { + return _maxHeight; + } + /// create new named style override Style createSubstyle(string id) { Style style = new Style(this, id); @@ -470,6 +493,6 @@ private __gshared Theme _currentTheme; static this() { _currentTheme = new Theme("default"); - Style button = _currentTheme.createSubstyle("BUTTON").backgroundImageId("btn_default_normal"); - button.alignment(Align.Center); + Style button = _currentTheme.createSubstyle("BUTTON").backgroundImageId("btn_default_normal").alignment(Align.Center); + Style text = _currentTheme.createSubstyle("TEXT").margins(Rect(3,3,3,3)).padding(Rect(3,3,3,3)); } diff --git a/src/dlangui/widgets/widget.d b/src/dlangui/widgets/widget.d index e5449832..86993302 100644 --- a/src/dlangui/widgets/widget.d +++ b/src/dlangui/widgets/widget.d @@ -37,17 +37,16 @@ class Widget { /// height measured by measure() protected int _measuredHeight; /// true to force layout - protected bool _needLayout; + protected bool _needLayout = true; /// true to force redraw - protected bool _needDraw; + protected bool _needDraw = true; /// parent widget protected Widget _parent; /// window (to be used for top level widgets only!) protected Window _window; - this() { - _needLayout = true; - _needDraw = true; + this(string ID = null) { + _id = id; } /// accessor to style - by lookup in theme by styleId (if style id is not set, theme base style will be used). @@ -82,7 +81,20 @@ class Widget { /// set margins for widget - override one from style @property Widget margins(Rect rc) { ownStyle.margins = rc; return this; } /// get padding (between background bounds and content of widget) - @property Rect padding() const { return style.padding; } + @property Rect padding() const { + // get max padding from style padding and background drawable padding + Rect p = style.padding; + Rect dp = style.backgroundDrawable.padding; + if (p.left < dp.left) + p.left = dp.left; + if (p.right < dp.right) + p.right = dp.right; + if (p.top < dp.top) + p.top = dp.top; + if (p.bottom < dp.bottom) + p.bottom = dp.bottom; + return p; + } /// set padding for widget - override one from style @property Widget padding(Rect rc) { ownStyle.padding = rc; return this; } /// returns background color @@ -127,7 +139,7 @@ class Widget { /// returns widget content text (override to support this) @property dstring text() { return ""; } /// sets widget content text (override to support this) - @property void text(dstring s) { } + @property Widget text(dstring s) { return this; } //================================================================== // Layout and drawing related methods