color widget

This commit is contained in:
Adam D. Ruppe 2017-04-05 20:15:52 -04:00
parent c60535deca
commit 06577dee6f
4 changed files with 473 additions and 40 deletions

View File

@ -1336,7 +1336,7 @@ struct Rectangle {
void floodFill(T)( void floodFill(T)(
T[] what, int width, int height, // the canvas to inspect T[] what, int width, int height, // the canvas to inspect
T target, T replacement, // fill params T target, T replacement, // fill params
int x, int y, bool delegate(int x, int y) additionalCheck) // the node int x, int y, bool delegate(int x, int y) @safe additionalCheck) // the node
{ {
T node = what[y * width + x]; T node = what[y * width + x];
@ -1344,6 +1344,9 @@ void floodFill(T)(
if(node != target) return; if(node != target) return;
if(additionalCheck is null)
additionalCheck = (int, int) => true;
if(!additionalCheck(x, y)) if(!additionalCheck(x, y))
return; return;

276
minigui.d
View File

@ -67,14 +67,17 @@ version(Windows) {
// use native widgets when available unless specifically asked otherwise // use native widgets when available unless specifically asked otherwise
version(custom_widgets) { version(custom_widgets) {
enum bool UsingCustomWidgets = true; enum bool UsingCustomWidgets = true;
enum bool UsingWin32Widgets = false;
} else { } else {
version = win32_widgets; version = win32_widgets;
enum bool UsingCustomWidgets = false; enum bool UsingCustomWidgets = false;
enum bool UsingWin32Widgets = true;
} }
// and native theming when needed // and native theming when needed
//version = win32_theming; //version = win32_theming;
} else { } else {
enum bool UsingCustomWidgets = true; enum bool UsingCustomWidgets = true;
enum bool UsingWin32Widgets = false;
version=custom_widgets; version=custom_widgets;
} }
@ -729,6 +732,8 @@ void recomputeChildLayout(string relevantMeasure)(Widget parent) {
int stretchinessSum; int stretchinessSum;
int lastMargin = 0; int lastMargin = 0;
// set initial size
foreach(child; parent.children) { foreach(child; parent.children) {
if(cast(StaticPosition) child) if(cast(StaticPosition) child)
continue; continue;
@ -748,13 +753,13 @@ void recomputeChildLayout(string relevantMeasure)(Widget parent) {
child.width = child.maxWidth(); child.width = child.maxWidth();
child.height = child.minHeight(); child.height = child.minHeight();
} else { } else {
if(child.height < 0)
child.height = 0;
child.height = parent.height - child.height = parent.height -
mixin("child.margin"~firstThingy~"()") - mixin("child.margin"~firstThingy~"()") -
mixin("child.margin"~secondThingy~"()") - mixin("child.margin"~secondThingy~"()") -
mixin("parent.padding"~firstThingy~"()") - mixin("parent.padding"~firstThingy~"()") -
mixin("parent.padding"~secondThingy~"()"); mixin("parent.padding"~secondThingy~"()");
if(child.height < 0)
child.height = 0;
if(child.height > child.maxHeight()) if(child.height > child.maxHeight())
child.height = child.maxHeight(); child.height = child.maxHeight();
child.width = child.minWidth(); child.width = child.minWidth();
@ -769,7 +774,7 @@ void recomputeChildLayout(string relevantMeasure)(Widget parent) {
stretchinessSum += mixin("child." ~ relevantMeasure ~ "Stretchiness()"); stretchinessSum += mixin("child." ~ relevantMeasure ~ "Stretchiness()");
} }
// stretch to fill space
while(spaceRemaining > 0 && stretchinessSum) { while(spaceRemaining > 0 && stretchinessSum) {
//import std.stdio; writeln("str ", stretchinessSum); //import std.stdio; writeln("str ", stretchinessSum);
auto spacePerChild = spaceRemaining / stretchinessSum; auto spacePerChild = spaceRemaining / stretchinessSum;
@ -809,6 +814,7 @@ void recomputeChildLayout(string relevantMeasure)(Widget parent) {
break; // apparently nothing more we can do break; // apparently nothing more we can do
} }
// position
lastMargin = 0; lastMargin = 0;
int currentPos = mixin("parent.padding"~firstThingy~"()"); int currentPos = mixin("parent.padding"~firstThingy~"()");
foreach(child; parent.children) { foreach(child; parent.children) {
@ -1250,6 +1256,9 @@ enum ScrollBarShowPolicy {
/++ /++
+/ +/
version(win32_widgets)
class ScrollableWidget : Widget { this(Widget parent = null) { super(parent); } } // TEMPORARY
else
class ScrollableWidget : Widget { class ScrollableWidget : Widget {
this(Widget parent = null) { this(Widget parent = null) {
horizontalScrollbarHolder = new FixedPosition(this); horizontalScrollbarHolder = new FixedPosition(this);
@ -1271,15 +1280,48 @@ class ScrollableWidget : Widget {
override void recomputeChildLayout() { override void recomputeChildLayout() {
bool both = showingVerticalScroll && showingHorizontalScroll; bool both = showingVerticalScroll && showingHorizontalScroll;
horizontalScrollbarHolder.width = this.width - (both ? 16 : 0); if(horizontalScrollbarHolder && verticalScrollbarHolder) {
horizontalScrollbarHolder.height = 16; horizontalScrollbarHolder.width = this.width - (both ? 16 : 0);
horizontalScrollbarHolder.x = 0; horizontalScrollbarHolder.height = 16;
horizontalScrollbarHolder.y = this.height - 16; horizontalScrollbarHolder.x = 0;
horizontalScrollbarHolder.y = this.height - 16;
verticalScrollbarHolder.width = 16;
verticalScrollbarHolder.height = this.height - (both ? 16 : 0);
verticalScrollbarHolder.x = this.width - 16;
verticalScrollbarHolder.y = 0;
{
int viewableScrollArea = viewportHeight;
int totalScrollArea = contentHeight;
int totalScrollBarArea = verticalScrollBar.thumb.height;
int thumbSize;
if(totalScrollArea)
thumbSize = viewableScrollArea * totalScrollBarArea / totalScrollArea;
else
thumbSize = 0;
if(thumbSize < 6)
thumbSize = 6;
verticalScrollBar.thumb.thumbHeight = thumbSize;
}
{
int viewableScrollArea = viewportWidth;
int totalScrollArea = contentWidth;
int totalScrollBarArea = horizontalScrollBar.thumb.width;
int thumbSize;
if(totalScrollArea)
thumbSize = viewableScrollArea * totalScrollBarArea / totalScrollArea;
else
thumbSize = 0;
if(thumbSize < 6)
thumbSize = 6;
horizontalScrollBar.thumb.thumbWidth = thumbSize;
}
}
verticalScrollbarHolder.width = 16;
verticalScrollbarHolder.height = this.height - (both ? 16 : 0);
verticalScrollbarHolder.x = this.width - 16;
verticalScrollbarHolder.y = 0;
super.recomputeChildLayout(); super.recomputeChildLayout();
} }
@ -1322,32 +1364,10 @@ class ScrollableWidget : Widget {
contentWidth = width; contentWidth = width;
contentHeight = height; contentHeight = height;
if(showingVerticalScroll && showingHorizontalScroll) { if(showingVerticalScroll || showingHorizontalScroll) {
recomputeChildLayout(); recomputeChildLayout();
} }
{
int viewableScrollArea = viewportHeight;
int totalScrollArea = contentHeight;
int totalScrollBarArea = verticalScrollBar.thumb.height;
int thumbSize = viewableScrollArea * totalScrollBarArea / totalScrollArea;
verticalScrollBar.thumb.thumbHeight = thumbSize;
if(showingVerticalScroll())
verticalScrollBar.redraw();
}
{
int viewableScrollArea = viewportWidth;
int totalScrollArea = contentWidth;
int totalScrollBarArea = horizontalScrollBar.thumb.width;
int thumbSize = viewableScrollArea * totalScrollBarArea / totalScrollArea;
horizontalScrollBar.thumb.thumbWidth = thumbSize;
if(showingHorizontalScroll())
horizontalScrollBar.redraw();
}
if(showingHorizontalScroll()) if(showingHorizontalScroll())
horizontalScrollbarHolder.hidden = false; horizontalScrollbarHolder.hidden = false;
@ -1358,6 +1378,10 @@ class ScrollableWidget : Widget {
else else
verticalScrollbarHolder.hidden = true; verticalScrollbarHolder.hidden = true;
if(showingVerticalScroll())
verticalScrollBar.redraw();
if(showingHorizontalScroll())
horizontalScrollBar.redraw();
} }
@ -1486,6 +1510,7 @@ abstract class ScrollbarBase : Widget {
} }
/// ///
version(custom_widgets)
class HorizontalScrollbar : ScrollbarBase { class HorizontalScrollbar : ScrollbarBase {
MouseTrackingWidget thumb; MouseTrackingWidget thumb;
@ -1538,7 +1563,8 @@ class HorizontalScrollbar : ScrollbarBase {
override int minWidth() { return 48; } override int minWidth() { return 48; }
} }
/// ///show
version(custom_widgets)
class VerticalScrollbar : ScrollbarBase { class VerticalScrollbar : ScrollbarBase {
MouseTrackingWidget thumb; MouseTrackingWidget thumb;
@ -1596,6 +1622,7 @@ class VerticalScrollbar : ScrollbarBase {
Concrete subclasses may include a scrollbar thumb and a volume control. Concrete subclasses may include a scrollbar thumb and a volume control.
+/ +/
version(custom_widgets)
class MouseTrackingWidget : Widget { class MouseTrackingWidget : Widget {
int mouseTrackerPosition; int mouseTrackerPosition;
@ -1625,11 +1652,31 @@ class MouseTrackingWidget : Widget {
addEventListener(EventType.mousedown, (Event event) { addEventListener(EventType.mousedown, (Event event) {
if(event.clientX >= positionX && event.clientX < positionX + thumbWidth && event.clientY >= positionY && event.clientY < positionY + thumbHeight) { if(event.clientX >= positionX && event.clientX < positionX + thumbWidth && event.clientY >= positionY && event.clientY < positionY + thumbHeight) {
dragging = true; dragging = true;
startMouseX = event.clientX; startMouseX = event.clientX - positionX;
startMouseY = event.clientY; startMouseY = event.clientY - positionY;
parentWindow.captureMouse(this); parentWindow.captureMouse(this);
} else { } else {
// FIXME if(orientation == Orientation.horizontal || orientation == Orientation.twoDimensional)
positionX = event.clientX - thumbWidth / 2;
if(orientation == Orientation.vertical || orientation == Orientation.twoDimensional)
positionY = event.clientY - thumbHeight / 2;
if(positionX + thumbWidth > this.width)
positionX = this.width - thumbWidth;
if(positionY + thumbHeight > this.height)
positionY = this.height - thumbHeight;
if(positionX < 0)
positionX = 0;
if(positionY < 0)
positionY = 0;
auto evt = new Event(EventType.change, this);
evt.sendDirectly();
redraw();
} }
}); });
@ -1659,7 +1706,7 @@ class MouseTrackingWidget : Widget {
} }
if(orientation == Orientation.horizontal || orientation == Orientation.twoDimensional) if(orientation == Orientation.horizontal || orientation == Orientation.twoDimensional)
positionX = event.clientX - startMouseX; positionX = event.clientX - startMouseX; // FIXME: click could be in the middle of it
if(orientation == Orientation.vertical || orientation == Orientation.twoDimensional) if(orientation == Orientation.vertical || orientation == Orientation.twoDimensional)
positionY = event.clientY - startMouseY; positionY = event.clientY - startMouseY;
@ -1702,6 +1749,98 @@ abstract class Layout : Widget {
} }
} }
/++
Makes all children minimum width and height, placing them down
left to right, top to bottom.
Useful if you want to make a list of buttons that automatically
wrap to a new line when necessary.
+/
class InlineBlockLayout : Layout {
this(Widget parent = null) { super(parent); }
override void recomputeChildLayout() {
registerMovement();
int x = this.paddingLeft, y = this.paddingTop;
int lineHeight;
int previousMargin = 0;
int previousMarginBottom = 0;
foreach(child; children) {
if(child.hidden)
continue;
if(cast(FixedPosition) child) {
child.recomputeChildLayout();
continue;
}
child.width = child.minWidth();
if(child.width == 0)
child.width = 32;
child.height = child.minHeight();
if(child.height == 0)
child.height = 32;
if(x + child.width + paddingRight > this.width) {
x = this.paddingLeft;
y += lineHeight;
lineHeight = 0;
previousMargin = 0;
previousMarginBottom = 0;
}
auto margin = child.marginLeft;
if(previousMargin > margin)
margin = previousMargin;
x += margin;
child.x = x;
child.y = y;
int marginTopApplied;
if(child.marginTop > previousMarginBottom) {
child.y += child.marginTop;
marginTopApplied = child.marginTop;
}
x += child.width;
previousMargin = child.marginRight;
if(child.marginBottom > previousMarginBottom)
previousMarginBottom = child.marginBottom;
auto h = child.height + previousMarginBottom + marginTopApplied;
if(h > lineHeight)
lineHeight = h;
child.recomputeChildLayout();
}
}
override int minWidth() {
int min;
foreach(child; children) {
auto cm = child.minWidth;
if(cm > min)
min = cm;
}
return min + paddingLeft + paddingRight;
}
override int minHeight() {
int min;
foreach(child; children) {
auto cm = child.minHeight;
if(cm > min)
min = cm;
}
return min + paddingTop + paddingBottom;
}
}
/// Stacks the widgets vertically, taking all the available width for each child. /// Stacks the widgets vertically, taking all the available width for each child.
class VerticalLayout : Layout { class VerticalLayout : Layout {
// intentionally blank - widget's default is vertical layout right now // intentionally blank - widget's default is vertical layout right now
@ -2033,6 +2172,7 @@ class Window : Widget {
auto event = new Event(ev.pressed ? "keydown" : "keyup", focusedWidget); auto event = new Event(ev.pressed ? "keydown" : "keyup", focusedWidget);
event.character = ev.character; event.character = ev.character;
event.key = ev.key; event.key = ev.key;
event.state = ev.modifierState;
event.shiftKey = (ev.modifierState & ModifierState.shift) ? true : false; event.shiftKey = (ev.modifierState & ModifierState.shift) ? true : false;
event.dispatch(); event.dispatch();
} }
@ -2146,6 +2286,15 @@ class Window : Widget {
win.eventLoop(0); win.eventLoop(0);
} }
override void show() {
win.show();
super.show();
}
override void hide() {
win.hide();
super.hide();
}
static Widget getFirstFocusable(Widget start) { static Widget getFirstFocusable(Widget start) {
if(start.tabStop && !start.hidden) if(start.tabStop && !start.hidden)
return start; return start;
@ -2160,6 +2309,48 @@ class Window : Widget {
} }
} }
/++
A dialog is a transient window that intends to get information from
the user before being dismissed.
+/
abstract class Dialog : Window {
///
this(int width, int height, string title = null) {
super(width, height, title);
}
///
abstract void OK();
///
void Cancel() {
this.close();
}
}
///
class LabeledLineEdit : Widget {
this(string label, Widget parent = null) {
super(parent);
tabStop = false;
auto hl = new HorizontalLayout(this);
this.label = new TextLabel(label, hl);
this.lineEdit = new LineEdit(hl);
}
TextLabel label; ///
LineEdit lineEdit; ///
override int minHeight() { return Window.lineHeight + 4; }
override int maxHeight() { return Window.lineHeight + 4; }
string content() {
return lineEdit.content;
}
void content(string c) {
return lineEdit.content(c);
}
}
/// ///
class MainWindow : Window { class MainWindow : Window {
this(string title = null) { this(string title = null) {
@ -3298,6 +3489,7 @@ int[2] getChildPositionRelativeToParentHwnd(Widget c) nothrow {
class TextLabel : Widget { class TextLabel : Widget {
override int maxHeight() { return Window.lineHeight; } override int maxHeight() { return Window.lineHeight; }
override int minHeight() { return Window.lineHeight; } override int minHeight() { return Window.lineHeight; }
override int minWidth() { return 32; }
string label; string label;
this(string label, Widget parent = null) { this(string label, Widget parent = null) {
@ -3322,6 +3514,7 @@ abstract class EditableTextWidget : ScrollableWidget {
super(parent); super(parent);
} }
override int minWidth() { return 16; }
override int minHeight() { return Window.lineHeight + 0; } // the +0 is to leave room for the padding override int minHeight() { return Window.lineHeight + 0; } // the +0 is to leave room for the padding
override int widthStretchiness() { return 3; } override int widthStretchiness() { return 3; }
@ -3422,6 +3615,9 @@ abstract class EditableTextWidget : ScrollableWidget {
caratTimer.destroy(); caratTimer.destroy();
caratTimer = null; caratTimer = null;
} }
auto evt = new Event(EventType.change, this);
evt.dispatch();
}; };
defaultEventHandlers["char"] = delegate (Widget _this, Event ev) { defaultEventHandlers["char"] = delegate (Widget _this, Event ev) {
@ -3680,6 +3876,7 @@ class Event {
private bool isBubbling; private bool isBubbling;
private void adjustScrolling() { private void adjustScrolling() {
version(custom_widgets) { // TEMP
viewportX = clientX; viewportX = clientX;
viewportY = clientY; viewportY = clientY;
if(auto se = cast(ScrollableWidget) srcElement) { if(auto se = cast(ScrollableWidget) srcElement) {
@ -3687,6 +3884,7 @@ class Event {
clientY += se.scrollOrigin.y; clientY += se.scrollOrigin.y;
} }
} }
}
/// this sends it only to the target. If you want propagation, use dispatch() instead. /// this sends it only to the target. If you want propagation, use dispatch() instead.
void sendDirectly() { void sendDirectly() {

View File

@ -0,0 +1,207 @@
/++
Displays a color-picker dialog box.
+/
module arsd.minigui_addons.color_dialog;
import arsd.minigui;
static if(UsingWin32Widgets)
pragma(lib, "comdlg32");
/++
+/
void showColorDialog(Window owner, Color current, void delegate(Color choice) onOK, void delegate() onCancel = null) {
static if(UsingWin32Widgets) {
import core.sys.windows.windows;
static COLORREF[16] customColors;
CHOOSECOLOR cc;
cc.lStructSize = cc.sizeof;
cc.hwndOwner = owner ? owner.win.impl.hwnd : null;
cc.lpCustColors = cast(LPDWORD) customColors.ptr;
cc.rgbResult = RGB(current.r, current.g, current.b);
cc.Flags = CC_FULLOPEN | CC_RGBINIT;
if(ChooseColor(&cc)) {
onOK(Color(GetRValue(cc.rgbResult), GetGValue(cc.rgbResult), GetBValue(cc.rgbResult)));
} else {
if(onCancel)
onCancel();
}
} else static if(UsingCustomWidgets) {
auto cpd = new ColorPickerDialog(current, onOK, owner);
cpd.show();
} else static assert(0);
}
/*
Hue / Saturation picker
Lightness Picker
Text selections
Graphical representation
Cancel OK
*/
static if(UsingCustomWidgets)
class ColorPickerDialog : Dialog {
static arsd.simpledisplay.Sprite hslImage;
static bool canUseImage;
void delegate(Color) onOK;
this(Color current, void delegate(Color) onOK, Window owner) {
super(360, 350, "Color picker");
this.onOK = onOK;
/*
statusBar.parts ~= new StatusBar.Part(140);
statusBar.parts ~= new StatusBar.Part(140);
statusBar.parts ~= new StatusBar.Part(140);
statusBar.parts ~= new StatusBar.Part(140);
this.addEventListener("mouseover", (Event ev) {
import std.conv;
this.statusBar.parts[2].content = to!string(ev.target.minHeight) ~ " - " ~ to!string(ev.target.maxHeight);
this.statusBar.parts[3].content = ev.target.toString();
});
*/
static if(UsingSimpledisplayX11)
// it is brutally slow over the network if we don't
// have xshm, so we've gotta do something else.
canUseImage = Image.impl.xshmAvailable;
else
canUseImage = true;
if(hslImage is null && canUseImage) {
auto img = new TrueColorImage(180, 128);
double h = 0.0, s = 1.0, l = 0.5;
foreach(y; 0 .. img.height) {
foreach(x; 0 .. img.width) {
img.imageData.colors[y * img.width + x] = Color.fromHsl(h,s,l);
h += 360.0 / img.width;
}
h = 0.0;
s -= 1.0 / img.height;
}
hslImage = new arsd.simpledisplay.Sprite(this.win, Image.fromMemoryImage(img));
}
auto t = this;
auto wid = new class Widget {
this() { super(t); }
override int minHeight() { return hslImage ? hslImage.height : 4; }
override int maxHeight() { return hslImage ? hslImage.height : 4; }
};
wid.paint = (ScreenPainter painter) {
if(hslImage)
hslImage.drawAt(painter, Point(0, 0));
};
auto vlRgb = new class VerticalLayout {
this() {
super(t);
}
override int maxWidth() { return 150; };
};
r = new LabeledLineEdit("Red:", vlRgb);
g = new LabeledLineEdit("Green:", vlRgb);
b = new LabeledLineEdit("Blue:", vlRgb);
a = new LabeledLineEdit("Alpha:", vlRgb);
import std.conv;
r.content = to!string(current.r);
g.content = to!string(current.g);
b.content = to!string(current.b);
a.content = to!string(current.a);
if(hslImage !is null)
wid.addEventListener("mousedown", (Event event) {
auto h = cast(double) event.clientX / hslImage.width * 360.0;
auto s = 1.0 - (cast(double) event.clientY / hslImage.height * 1.0);
auto l = 0.5;
auto color = Color.fromHsl(h, s, l);
r.content = to!string(color.r);
g.content = to!string(color.g);
b.content = to!string(color.b);
a.content = to!string(color.a);
});
Color currentColor() {
try {
return Color(to!int(r.content), to!int(g.content), to!int(b.content), to!int(a.content));
} catch(Exception e) {
return Color.transparent;
}
}
this.addEventListener("keydown", (Event event) {
if(event.key == Key.Enter)
OK();
if(event.character == Key.Escape)
Cancel();
});
this.addEventListener("change", {
redraw();
});
auto currentColorWidget = new Widget(this);
currentColorWidget.paint = (ScreenPainter painter) {
auto c = currentColor();
auto c1 = alphaBlend(c, Color(64, 64, 64));
auto c2 = alphaBlend(c, Color(192, 192, 192));
painter.outlineColor = c1;
painter.fillColor = c1;
painter.drawRectangle(Point(0, 0), currentColorWidget.width / 2, currentColorWidget.height / 2);
painter.drawRectangle(Point(currentColorWidget.width / 2, currentColorWidget.height / 2), currentColorWidget.width / 2, currentColorWidget.height / 2);
painter.outlineColor = c2;
painter.fillColor = c2;
painter.drawRectangle(Point(currentColorWidget.width / 2, 0), currentColorWidget.width / 2, currentColorWidget.height / 2);
painter.drawRectangle(Point(0, currentColorWidget.height / 2), currentColorWidget.width / 2, currentColorWidget.height / 2);
};
auto hl = new HorizontalLayout(this);
auto cancelButton = new Button("Cancel", hl);
auto okButton = new Button("OK", hl);
recomputeChildLayout(); // FIXME hack
cancelButton.addEventListener(EventType.triggered, &Cancel);
okButton.addEventListener(EventType.triggered, &OK);
}
LabeledLineEdit r;
LabeledLineEdit g;
LabeledLineEdit b;
LabeledLineEdit a;
override void OK() {
import std.conv;
try {
onOK(Color(to!int(r.content), to!int(g.content), to!int(b.content), to!int(a.content)));
this.close();
} catch(Exception e) {
auto mb = new MessageBox("Bad value");
mb.show();
}
}
}

25
minigui_addons/package.d Normal file
View File

@ -0,0 +1,25 @@
/++
This package consists of additional widgets for [arsd.minigui].
Each module stands alone on top of minigui.d; none in this package
depend on each other, so you can pick and choose the modules that
look useful to you and ignore the others.
These modules may or may not expose native widgets, refer to the
documentation in each individual to see what it does.
When writing a minigui addon module, keep the following in mind:
$(LIST
* Use `static if(UsingWin32Widgets)` and `static if(UsingCustomWidgets)`
if you want to provide both native Windows and custom drawn alternatives.
Do NOT use `version` because versions are not imported across modules.
* Similarly, if you need to write platform-specific code, you can use
`static if(UsingSimpledisplayX11)` to check for X. However, here,
`version(Windows)` also works pretty well.
)
+/
module arsd.minigui_addons;