fix linear layout implementation

This commit is contained in:
Vadim Lopatin 2014-03-08 22:09:35 +04:00
parent 3661f7f9fb
commit 1341bb6e71
7 changed files with 218 additions and 141 deletions

View File

@ -1,12 +1,12 @@
module winmain; module winmain;
import dlangui.all; import dlangui.all;
import std.stdio; import std.stdio;
/// workaround for link issue when WinMain is located in library /// workaround for link issue when WinMain is located in library
version(Windows) { version(Windows) {
import win32.windows; import win32.windows;
import dlangui.platforms.windows.winapp; import dlangui.platforms.windows.winapp;
extern (Windows) extern (Windows)
int WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, int WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPSTR lpCmdLine, int nCmdShow) LPSTR lpCmdLine, int nCmdShow)
@ -15,24 +15,35 @@ version(Windows) {
lpCmdLine, nCmdShow); lpCmdLine, nCmdShow);
} }
} }
/// entry point for dlangui based application /// entry point for dlangui based application
extern (C) int UIAppMain(string[] args) { extern (C) int UIAppMain(string[] args) {
// setup resource dir // setup resource dir
string resourceDir = exePath() ~ "..\\res\\"; string resourceDir = exePath() ~ "..\\res\\";
string[] imageDirs = [ string[] imageDirs = [
resourceDir resourceDir
]; ];
drawableCache.resourcePaths = imageDirs; drawableCache.resourcePaths = imageDirs;
// create window // create window
Window window = Platform.instance().createWindow("My Window", null); Window window = Platform.instance().createWindow("My Window", null);
Widget myWidget = (new Button()).textColor(0x40FF4000); LinearLayout layout = new LinearLayout();
myWidget.text = "Some strange text string. 1234567890"; layout.addChild((new TextWidget()).textColor(0x00802000).text("Text widget 0"));
window.mainWidget = myWidget; layout.addChild((new TextWidget()).textColor(0x40FF4000).text("Text widget"));
window.show(); layout.addChild((new Button()).textColor(0x40FF4000).text("Button1"));
window.windowCaption = "New Window Caption"; layout.addChild((new Button()).textColor(0x000000FF).text("Button2"));
layout.addChild((new TextWidget()).textColor(0x40FF4000).text("Text widget"));
// run message loop layout.addChild((new ImageWidget()).drawableId("exit").padding(Rect(5,5,5,5)));
return Platform.instance().enterMessageLoop(); 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();
}

View File

@ -1,8 +1,10 @@
module dlangui.all; module dlangui.all;
public import dlangui.core.logger;
public import dlangui.core.types;
public import dlangui.platforms.common.platform; public import dlangui.platforms.common.platform;
public import dlangui.graphics.images; public import dlangui.graphics.images;
public import dlangui.widgets.widget; public import dlangui.widgets.widget;
public import dlangui.widgets.controls; public import dlangui.widgets.controls;
public import dlangui.core.logger; public import dlangui.widgets.layouts;
public import dlangui.graphics.fonts; public import dlangui.graphics.fonts;

View File

@ -1,68 +1,68 @@
module dlangui.platforms.common.platform; module dlangui.platforms.common.platform;
import dlangui.widgets.widget; import dlangui.widgets.widget;
import dlangui.graphics.drawbuf; import dlangui.graphics.drawbuf;
import std.file; import std.file;
public class Window { public class Window {
int _dx; int _dx;
int _dy; int _dy;
Widget _mainWidget; Widget _mainWidget;
public @property int width() { return _dx; } public @property int width() { return _dx; }
public @property int height() { return _dy; } public @property int height() { return _dy; }
public @property Widget mainWidget() { return _mainWidget; } public @property Widget mainWidget() { return _mainWidget; }
public @property void mainWidget(Widget widget) { public @property void mainWidget(Widget widget) {
if (_mainWidget !is null) if (_mainWidget !is null)
_mainWidget.window = null; _mainWidget.window = null;
_mainWidget = widget; _mainWidget = widget;
if (_mainWidget !is null) if (_mainWidget !is null)
_mainWidget.window = this; _mainWidget.window = this;
} }
abstract public void show(); abstract public void show();
abstract public @property string windowCaption(); abstract public @property string windowCaption();
abstract public @property void windowCaption(string caption); abstract public @property void windowCaption(string caption);
public void onResize(int width, int height) { public void onResize(int width, int height) {
if (_dx == width && _dy == height) if (_dx == width && _dy == height)
return; return;
_dx = width; _dx = width;
_dy = height; _dy = height;
if (_mainWidget !is null) { if (_mainWidget !is null) {
_mainWidget.measure(_dx, _dy); _mainWidget.measure(_dx, _dy);
_mainWidget.layout(Rect(0, 0, _dx, _dy)); _mainWidget.layout(Rect(0, 0, _dx, _dy));
} }
} }
public void onDraw(DrawBuf buf) { public void onDraw(DrawBuf buf) {
if (_mainWidget !is null) { if (_mainWidget !is null) {
_mainWidget.onDraw(buf); _mainWidget.onDraw(buf);
} }
} }
} }
public class Platform { public class Platform {
static __gshared Platform _instance; static __gshared Platform _instance;
public static void setInstance(Platform instance) { public static void setInstance(Platform instance) {
_instance = instance; _instance = instance;
} }
public static Platform instance() { public static Platform instance() {
return _instance; return _instance;
} }
abstract public Window createWindow(string windowCaption, Window parent); abstract public Window createWindow(string windowCaption, Window parent);
abstract public int enterMessageLoop(); abstract public int enterMessageLoop();
} }
version (Windows) { version (Windows) {
immutable char PATH_DELIMITER = '\\'; immutable char PATH_DELIMITER = '\\';
} }
version (Unix) { version (Unix) {
immutable char PATH_DELIMITER = '/'; immutable char PATH_DELIMITER = '/';
} }
/// returns current executable path only, including last path delimiter /// returns current executable path only, including last path delimiter
string exePath() { string exePath() {
string path = thisExePath(); string path = thisExePath();
int lastSlash = 0; int lastSlash = 0;
for (int i = 0; i < path.length; i++) for (int i = 0; i < path.length; i++)
if (path[i] == PATH_DELIMITER) if (path[i] == PATH_DELIMITER)
lastSlash = i; lastSlash = i;
return path[0 .. lastSlash + 1]; return path[0 .. lastSlash + 1];
} }

View File

@ -4,16 +4,20 @@ import dlangui.widgets.widget;
/// static text widget /// static text widget
class TextWidget : Widget { class TextWidget : Widget {
this(string ID = null) {
super(ID);
styleId = "TEXT";
}
protected dstring _text; protected dstring _text;
/// get widget text /// get widget text
override @property dstring text() { return _text; } override @property dstring text() { return _text; }
/// set text to show /// set text to show
override @property void text(dstring s) { override @property Widget text(dstring s) {
_text = s; _text = s;
requestLayout(); requestLayout();
return this;
} }
override void measure(int parentWidth, int parentHeight) { override void measure(int parentWidth, int parentHeight) {
_measuredWidth = _measuredHeight = 0;
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);
@ -24,8 +28,8 @@ class TextWidget : Widget {
super.onDraw(buf); super.onDraw(buf);
Rect rc = _pos; Rect rc = _pos;
applyMargins(rc); applyMargins(rc);
applyPadding(rc);
ClipRectSaver(buf, rc); ClipRectSaver(buf, rc);
applyPadding(rc);
FontRef font = font(); FontRef font = font();
Point sz = font.textSize(text); Point sz = font.textSize(text);
applyAlign(rc, sz); applyAlign(rc, sz);
@ -39,6 +43,10 @@ class ImageWidget : Widget {
protected string _drawableId; protected string _drawableId;
protected DrawableRef _drawable; protected DrawableRef _drawable;
this(string ID = null) {
super(ID);
}
/// get drawable image id /// get drawable image id
@property string drawableId() { return _drawableId; } @property string drawableId() { return _drawableId; }
/// set drawable image id /// set drawable image id
@ -79,8 +87,8 @@ class ImageWidget : Widget {
super.onDraw(buf); super.onDraw(buf);
Rect rc = _pos; Rect rc = _pos;
applyMargins(rc); applyMargins(rc);
applyPadding(rc);
ClipRectSaver(buf, rc); ClipRectSaver(buf, rc);
applyPadding(rc);
DrawableRef img = drawable; DrawableRef img = drawable;
if (!img.isNull) { if (!img.isNull) {
Point sz; Point sz;
@ -95,16 +103,15 @@ class ImageWidget : Widget {
class Button : Widget { class Button : Widget {
protected dstring _text; protected dstring _text;
override @property dstring text() { return _text; } override @property dstring text() { return _text; }
override @property void text(dstring s) { _text = s; } override @property Widget text(dstring s) { _text = s; requestLayout(); return this; }
this() { this(string ID = null) {
super(ID);
styleId = "BUTTON"; styleId = "BUTTON";
} }
override void measure(int width, int height) { override void measure(int parentWidth, int parentHeight) {
_measuredWidth = _measuredHeight = 0; FontRef font = font();
} Point sz = font.textSize(text);
override void layout(Rect rc) { measuredContent(parentWidth, parentHeight, sz.x, sz.y);
_pos = rc;
_needLayout = false;
} }
override void onDraw(DrawBuf buf) { override void onDraw(DrawBuf buf) {
super.onDraw(buf); super.onDraw(buf);

View File

@ -74,7 +74,6 @@ class LinearLayout : WidgetGroup {
int contentHeight = 0; int contentHeight = 0;
if (orientation == Orientation.Vertical) { if (orientation == Orientation.Vertical) {
// Vertical // Vertical
int position = 0;
int totalSize = 0; int totalSize = 0;
int delta = 0; int delta = 0;
int resizableSize = 0; int resizableSize = 0;
@ -89,16 +88,17 @@ class LinearLayout : WidgetGroup {
if (item.visibility == Visibility.Gone) if (item.visibility == Visibility.Gone)
continue; continue;
visibleCount++; visibleCount++;
totalSize += item.measuredHeight; int weight = item.layoutWeight;
int size = item.measuredHeight;
totalSize += size;
if (maxItem < item.measuredWidth) if (maxItem < item.measuredWidth)
maxItem = item.measuredWidth; maxItem = item.measuredWidth;
int weight = item.layoutWeight;
if (item.layoutHeight == FILL_PARENT) { if (item.layoutHeight == FILL_PARENT) {
resizableWeight += weight; resizableWeight += weight;
resizableSize += item.measuredHeight; resizableSize += size * weight;
} else { } else {
nonresizableWeight += weight; nonresizableWeight += weight;
nonresizableSize += item.measuredHeight; nonresizableSize += size * weight;
} }
} }
if (layoutWidth == WRAP_CONTENT && maxItem < rc.width) if (layoutWidth == WRAP_CONTENT && maxItem < rc.width)
@ -107,42 +107,59 @@ class LinearLayout : WidgetGroup {
contentWidth = rc.width; contentWidth = rc.width;
if (layoutHeight == FILL_PARENT || totalSize > rc.height) if (layoutHeight == FILL_PARENT || totalSize > rc.height)
delta = rc.height - totalSize; // total space to add to fit delta = rc.height - totalSize; // total space to add to fit
// calculate resize options and scale
bool needForceResize = false; bool needForceResize = false;
bool needResize = false; bool needResize = false;
int scaleFactor = 10000; // per weight unit int scaleFactor = 10000; // per weight unit
if (delta != 0 && visibleCount > 0) { if (delta != 0 && visibleCount > 0) {
// need resize of some children // need resize of some children
needResize = true; 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) if (needForceResize)
scaleFactor = 10000 * rc.height / (resizableSize + nonresizableSize) / (nonresizableWeight + resizableWeight); scaleFactor = 10000 * delta / (nonresizableSize + resizableSize);
else 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++) { for (int i = 0; i < _children.count; i++) {
Widget item = _children.get(i); Widget item = _children.get(i);
if (item.visibility == Visibility.Gone) if (item.visibility == Visibility.Gone)
continue; continue;
int weight = item.layoutWeight; if (item.layoutHeight == FILL_PARENT || needForceResize) {
if (item.layoutHeight == FILL_PARENT) { lastResized = item;
resizableWeight += weight;
resizableSize += item.measuredHeight;
} else {
nonresizableWeight += weight;
nonresizableSize += item.measuredHeight;
} }
} }
// final resize and layout of children
int position = 0;
int deltaTotal = 0;
for (int i = 0; i < _children.count; i++) { for (int i = 0; i < _children.count; i++) {
Widget item = _children.get(i); Widget item = _children.get(i);
if (item.visibility == Visibility.Gone) if (item.visibility == Visibility.Gone)
continue; continue;
int layoutSize = item.layoutHeight; int layoutSize = item.layoutHeight;
totalWeight += item.measuredHeight;
int weight = item.layoutWeight; int weight = item.layoutWeight;
if (layoutSize) { int size = item.measuredHeight;
resizableWeight += weight; if (needResize && (layoutSize == FILL_PARENT || needForceResize)) {
resizableSize += item.measuredHeight; // 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 { } else {
// Horizontal // Horizontal
@ -157,8 +174,13 @@ class LinearLayout : WidgetGroup {
Rect rc = _pos; Rect rc = _pos;
applyMargins(rc); applyMargins(rc);
applyPadding(rc); applyPadding(rc);
// TODO ClipRectSaver(buf, rc);
_needDraw = false; for (int i = 0; i < _children.count; i++) {
Widget item = _children.get(i);
if (item.visibility != Visibility.Visible)
continue;
item.onDraw(buf);
}
} }
} }

View File

@ -447,6 +447,29 @@ class Theme : Style {
return 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 /// create new named style
override Style createSubstyle(string id) { override Style createSubstyle(string id) {
Style style = new Style(this, id); Style style = new Style(this, id);
@ -470,6 +493,6 @@ private __gshared Theme _currentTheme;
static this() { static this() {
_currentTheme = new Theme("default"); _currentTheme = new Theme("default");
Style button = _currentTheme.createSubstyle("BUTTON").backgroundImageId("btn_default_normal"); Style button = _currentTheme.createSubstyle("BUTTON").backgroundImageId("btn_default_normal").alignment(Align.Center);
button.alignment(Align.Center); Style text = _currentTheme.createSubstyle("TEXT").margins(Rect(3,3,3,3)).padding(Rect(3,3,3,3));
} }

View File

@ -37,17 +37,16 @@ class Widget {
/// height measured by measure() /// height measured by measure()
protected int _measuredHeight; protected int _measuredHeight;
/// true to force layout /// true to force layout
protected bool _needLayout; protected bool _needLayout = true;
/// true to force redraw /// true to force redraw
protected bool _needDraw; protected bool _needDraw = true;
/// parent widget /// parent widget
protected Widget _parent; protected Widget _parent;
/// window (to be used for top level widgets only!) /// window (to be used for top level widgets only!)
protected Window _window; protected Window _window;
this() { this(string ID = null) {
_needLayout = true; _id = id;
_needDraw = true;
} }
/// accessor to style - by lookup in theme by styleId (if style id is not set, theme base style will be used). /// 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 /// set margins for widget - override one from style
@property Widget margins(Rect rc) { ownStyle.margins = rc; return this; } @property Widget margins(Rect rc) { ownStyle.margins = rc; return this; }
/// get padding (between background bounds and content of widget) /// 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 /// 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
@ -127,7 +139,7 @@ class Widget {
/// returns widget content text (override to support this) /// returns widget content text (override to support this)
@property dstring text() { return ""; } @property dstring text() { return ""; }
/// sets widget content text (override to support this) /// 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 // Layout and drawing related methods