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)(
T[] what, int width, int height, // the canvas to inspect
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];
@ -1344,6 +1344,9 @@ void floodFill(T)(
if(node != target) return;
if(additionalCheck is null)
additionalCheck = (int, int) => true;
if(!additionalCheck(x, y))
return;

276
minigui.d
View File

@ -67,14 +67,17 @@ version(Windows) {
// use native widgets when available unless specifically asked otherwise
version(custom_widgets) {
enum bool UsingCustomWidgets = true;
enum bool UsingWin32Widgets = false;
} else {
version = win32_widgets;
enum bool UsingCustomWidgets = false;
enum bool UsingWin32Widgets = true;
}
// and native theming when needed
//version = win32_theming;
} else {
enum bool UsingCustomWidgets = true;
enum bool UsingWin32Widgets = false;
version=custom_widgets;
}
@ -729,6 +732,8 @@ void recomputeChildLayout(string relevantMeasure)(Widget parent) {
int stretchinessSum;
int lastMargin = 0;
// set initial size
foreach(child; parent.children) {
if(cast(StaticPosition) child)
continue;
@ -748,13 +753,13 @@ void recomputeChildLayout(string relevantMeasure)(Widget parent) {
child.width = child.maxWidth();
child.height = child.minHeight();
} else {
if(child.height < 0)
child.height = 0;
child.height = parent.height -
mixin("child.margin"~firstThingy~"()") -
mixin("child.margin"~secondThingy~"()") -
mixin("parent.padding"~firstThingy~"()") -
mixin("parent.padding"~secondThingy~"()");
if(child.height < 0)
child.height = 0;
if(child.height > child.maxHeight())
child.height = child.maxHeight();
child.width = child.minWidth();
@ -769,7 +774,7 @@ void recomputeChildLayout(string relevantMeasure)(Widget parent) {
stretchinessSum += mixin("child." ~ relevantMeasure ~ "Stretchiness()");
}
// stretch to fill space
while(spaceRemaining > 0 && stretchinessSum) {
//import std.stdio; writeln("str ", stretchinessSum);
auto spacePerChild = spaceRemaining / stretchinessSum;
@ -809,6 +814,7 @@ void recomputeChildLayout(string relevantMeasure)(Widget parent) {
break; // apparently nothing more we can do
}
// position
lastMargin = 0;
int currentPos = mixin("parent.padding"~firstThingy~"()");
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 {
this(Widget parent = null) {
horizontalScrollbarHolder = new FixedPosition(this);
@ -1271,15 +1280,48 @@ class ScrollableWidget : Widget {
override void recomputeChildLayout() {
bool both = showingVerticalScroll && showingHorizontalScroll;
horizontalScrollbarHolder.width = this.width - (both ? 16 : 0);
horizontalScrollbarHolder.height = 16;
horizontalScrollbarHolder.x = 0;
horizontalScrollbarHolder.y = this.height - 16;
if(horizontalScrollbarHolder && verticalScrollbarHolder) {
horizontalScrollbarHolder.width = this.width - (both ? 16 : 0);
horizontalScrollbarHolder.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();
}
@ -1322,32 +1364,10 @@ class ScrollableWidget : Widget {
contentWidth = width;
contentHeight = height;
if(showingVerticalScroll && showingHorizontalScroll) {
if(showingVerticalScroll || showingHorizontalScroll) {
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())
horizontalScrollbarHolder.hidden = false;
@ -1358,6 +1378,10 @@ class ScrollableWidget : Widget {
else
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 {
MouseTrackingWidget thumb;
@ -1538,7 +1563,8 @@ class HorizontalScrollbar : ScrollbarBase {
override int minWidth() { return 48; }
}
///
///show
version(custom_widgets)
class VerticalScrollbar : ScrollbarBase {
MouseTrackingWidget thumb;
@ -1596,6 +1622,7 @@ class VerticalScrollbar : ScrollbarBase {
Concrete subclasses may include a scrollbar thumb and a volume control.
+/
version(custom_widgets)
class MouseTrackingWidget : Widget {
int mouseTrackerPosition;
@ -1625,11 +1652,31 @@ class MouseTrackingWidget : Widget {
addEventListener(EventType.mousedown, (Event event) {
if(event.clientX >= positionX && event.clientX < positionX + thumbWidth && event.clientY >= positionY && event.clientY < positionY + thumbHeight) {
dragging = true;
startMouseX = event.clientX;
startMouseY = event.clientY;
startMouseX = event.clientX - positionX;
startMouseY = event.clientY - positionY;
parentWindow.captureMouse(this);
} 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)
positionX = event.clientX - startMouseX;
positionX = event.clientX - startMouseX; // FIXME: click could be in the middle of it
if(orientation == Orientation.vertical || orientation == Orientation.twoDimensional)
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.
class VerticalLayout : Layout {
// 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);
event.character = ev.character;
event.key = ev.key;
event.state = ev.modifierState;
event.shiftKey = (ev.modifierState & ModifierState.shift) ? true : false;
event.dispatch();
}
@ -2146,6 +2286,15 @@ class Window : Widget {
win.eventLoop(0);
}
override void show() {
win.show();
super.show();
}
override void hide() {
win.hide();
super.hide();
}
static Widget getFirstFocusable(Widget start) {
if(start.tabStop && !start.hidden)
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 {
this(string title = null) {
@ -3298,6 +3489,7 @@ int[2] getChildPositionRelativeToParentHwnd(Widget c) nothrow {
class TextLabel : Widget {
override int maxHeight() { return Window.lineHeight; }
override int minHeight() { return Window.lineHeight; }
override int minWidth() { return 32; }
string label;
this(string label, Widget parent = null) {
@ -3322,6 +3514,7 @@ abstract class EditableTextWidget : ScrollableWidget {
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 widthStretchiness() { return 3; }
@ -3422,6 +3615,9 @@ abstract class EditableTextWidget : ScrollableWidget {
caratTimer.destroy();
caratTimer = null;
}
auto evt = new Event(EventType.change, this);
evt.dispatch();
};
defaultEventHandlers["char"] = delegate (Widget _this, Event ev) {
@ -3680,6 +3876,7 @@ class Event {
private bool isBubbling;
private void adjustScrolling() {
version(custom_widgets) { // TEMP
viewportX = clientX;
viewportY = clientY;
if(auto se = cast(ScrollableWidget) srcElement) {
@ -3687,6 +3884,7 @@ class Event {
clientY += se.scrollOrigin.y;
}
}
}
/// this sends it only to the target. If you want propagation, use dispatch() instead.
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;