mirror of https://github.com/adamdruppe/arsd.git
2785 lines
69 KiB
D
2785 lines
69 KiB
D
// http://msdn.microsoft.com/en-us/library/windows/desktop/bb775498%28v=vs.85%29.aspx
|
|
|
|
/++
|
|
minigui is a smallish GUI widget library, aiming to be on par with at least
|
|
HTML4 forms and a few other expected gui components. It uses native controls
|
|
on Windows and does its own thing on Linux (Mac is not currently supported but
|
|
may be later, and should use native controls) to keep size down.
|
|
|
|
Its #1 goal is to be useful without being large and complicated like GTK and Qt.
|
|
I love Qt, if you want something full featured, use it! But if you want something
|
|
you can just drop into a small project and expect the basics to work without outside
|
|
dependencies, hopefully minigui will work for you.
|
|
|
|
The event model is similar to what you use in the browser with Javascript and the
|
|
layout engine tries to automatically fit things in.
|
|
|
|
|
|
FOR BEST RESULTS: be sure to link with the appropriate subsystem command
|
|
`-L/SUBSYSTEM:WINDOWS:5.0`, for example, because otherwise you'll get a
|
|
console and other visual bugs.
|
|
|
|
Examples:
|
|
+/
|
|
module arsd.minigui;
|
|
|
|
/*
|
|
|
|
The main goals of minigui.d are to:
|
|
1) Provide basic widgets that just work in a lightweight lib.
|
|
I basically want things comparable to a plain HTML form,
|
|
plus the easy and obvious things you expect from Windows
|
|
apps like a menu.
|
|
2) Use native things when possible for best functionality with
|
|
least library weight.
|
|
3) Give building blocks to provide easy extension for your
|
|
custom widgets, or hooking into additional native widgets
|
|
I didn't wrap.
|
|
4) Provide interfaces for easy interaction between third
|
|
party minigui extensions. (event model, perhaps
|
|
signals/slots, drop-in ease of use bits.)
|
|
5) Zero non-system dependencies, including Phobos as much as
|
|
I reasonably can. It must only import arsd.color and
|
|
my simpledisplay.d. If you need more, it will have to be
|
|
an extension module.
|
|
6) An easy layout system that generally works.
|
|
|
|
A stretch goal is to make it easy to make gui forms with code,
|
|
some kind of resource file (xml?) and even a wysiwyg designer.
|
|
|
|
Another stretch goal is to make it easy to hook data into the gui,
|
|
including from reflection. So like auto-generate a form from a
|
|
function signature or struct definition, or show a list from an
|
|
array that automatically updates as the array is changed. Then,
|
|
your program focuses on the data more than the gui interaction.
|
|
|
|
|
|
|
|
STILL NEEDED:
|
|
* combo box. (this is diff than select because you can free-form edit too. more like a lineedit with autoselect)
|
|
* slider
|
|
* listbox
|
|
* spinner
|
|
* label?
|
|
* rich text
|
|
*/
|
|
|
|
abstract class ComboboxBase : Widget {
|
|
// if the user can enter arbitrary data, we want to use 2 == CBS_DROPDOWN
|
|
// or to always show the list, we want CBS_SIMPLE == 1
|
|
version(win32_widgets)
|
|
this(uint style, Widget parent = null) {
|
|
super(parent);
|
|
parentWindow = parent.parentWindow;
|
|
createWin32Window(this, "ComboBox", null, style);
|
|
}
|
|
|
|
private string[] options;
|
|
private int selection = -1;
|
|
|
|
void addOption(string s) {
|
|
options ~= s;
|
|
version(win32_widgets)
|
|
SendMessageA(hwnd, 323 /*CB_ADDSTRING*/, 0, cast(LPARAM) toStringzInternal(s));
|
|
}
|
|
|
|
void setSelection(int idx) {
|
|
selection = idx;
|
|
version(win32_widgets)
|
|
SendMessageA(hwnd, 334 /*CB_SETCURSEL*/, idx, 0);
|
|
}
|
|
|
|
version(win32_widgets)
|
|
override void handleWmCommand(ushort cmd, ushort id) {
|
|
selection = SendMessageA(hwnd, 327 /* CB_GETCURSEL */, 0, 0);
|
|
auto event = new Event("changed", this);
|
|
event.dispatch();
|
|
}
|
|
}
|
|
|
|
class DropDownSelection : ComboboxBase {
|
|
this(Widget parent = null) {
|
|
version(win32_widgets)
|
|
super(3 /* CBS_DROPDOWNLIST */, parent);
|
|
}
|
|
}
|
|
|
|
class FreeEntrySelection : ComboboxBase {
|
|
this(Widget parent = null) {
|
|
version(win32_widgets)
|
|
super(2 /* CBS_DROPDOWN */, parent);
|
|
}
|
|
}
|
|
|
|
class ComboBox : ComboboxBase {
|
|
this(Widget parent = null) {
|
|
version(win32_widgets)
|
|
super(1 /* CBS_SIMPLE */, parent);
|
|
}
|
|
}
|
|
|
|
|
|
/+
|
|
class Spinner : Widget {
|
|
version(win32_widgets)
|
|
this(Widget parent = null) {
|
|
super(parent);
|
|
parentWindow = parent.parentWindow;
|
|
auto hlayout = new HorizontalLayout(this);
|
|
lineEdit = new LineEdit(hlayout);
|
|
upDownControl = new UpDownControl(hlayout);
|
|
}
|
|
|
|
LineEdit lineEdit;
|
|
UpDownControl upDownControl;
|
|
}
|
|
|
|
class UpDownControl : Widget {
|
|
version(win32_widgets)
|
|
this(Widget parent = null) {
|
|
super(parent);
|
|
parentWindow = parent.parentWindow;
|
|
createWin32Window(this, "msctls_updown32", null, 4/*UDS_ALIGNRIGHT*/| 2 /* UDS_SETBUDDYINT */ | 16 /* UDS_AUTOBUDDY */ | 32 /* UDS_ARROWKEYS */);
|
|
}
|
|
|
|
override int minHeight() { return Window.lineHeight; }
|
|
override int maxHeight() { return Window.lineHeight * 3/2; }
|
|
|
|
override int minWidth() { return Window.lineHeight * 3/2; }
|
|
override int maxWidth() { return Window.lineHeight * 3/2; }
|
|
}
|
|
+/
|
|
|
|
class DataView : Widget {
|
|
// this is the omnibus data viewer
|
|
// the internal data layout is something like:
|
|
// string[string][] but also each node can have parents
|
|
}
|
|
|
|
|
|
// http://msdn.microsoft.com/en-us/library/windows/desktop/bb775491(v=vs.85).aspx#PROGRESS_CLASS
|
|
|
|
// http://svn.dsource.org/projects/bindings/trunk/win32/commctrl.d
|
|
|
|
// FIXME: menus should prolly capture the mouse. ugh i kno.
|
|
|
|
public import arsd.simpledisplay;
|
|
|
|
version(Windows)
|
|
import core.sys.windows.windows;
|
|
|
|
// this is a hack to call the original window procedure on native win32 widgets if our event listener thing prevents default.
|
|
private bool lastDefaultPrevented;
|
|
|
|
version(Windows) {
|
|
// use native widgets when available unless specifically asked otherwise
|
|
version(custom_widgets) {
|
|
enum bool UsingCustomWidgets = true;
|
|
} else {
|
|
version = win32_widgets;
|
|
enum bool UsingCustomWidgets = false;
|
|
}
|
|
// and native theming when needed
|
|
//version = win32_theming;
|
|
} else {
|
|
enum bool UsingCustomWidgets = true;
|
|
}
|
|
|
|
/*
|
|
TextEdit needs:
|
|
|
|
* carat manipulation
|
|
* selection control
|
|
* convenience functions for appendText, insertText, insertTextAtCarat, etc.
|
|
|
|
For example:
|
|
|
|
connect(paste, &textEdit.insertTextAtCarat);
|
|
|
|
would be nice.
|
|
|
|
|
|
|
|
I kinda want an omnibus dataview that combines list, tree,
|
|
and table - it can be switched dynamically between them.
|
|
|
|
Flattening policy: only show top level, show recursive, show grouped
|
|
List styles: plain list (e.g. <ul>), tiles (some details next to it), icons (like Windows explorer)
|
|
|
|
Single select, multi select, organization, drag+drop
|
|
*/
|
|
|
|
//static if(UsingSimpledisplayX11)
|
|
version(win32_widgets) {}
|
|
else
|
|
enum windowBackgroundColor = Color(220, 220, 220);
|
|
|
|
private const(char)* toStringzInternal(string s) { return (s ~ '\0').ptr; }
|
|
private const(wchar)* toWstringzInternal(in char[] s) {
|
|
wchar[] str;
|
|
str.reserve(s.length + 1);
|
|
foreach(dchar ch; s)
|
|
str ~= ch;
|
|
str ~= '\0';
|
|
return str.ptr;
|
|
}
|
|
|
|
class Action {
|
|
version(win32_widgets) {
|
|
int id;
|
|
static int lastId = 9000;
|
|
static Action[int] mapping;
|
|
}
|
|
|
|
this(string label, ushort icon = 0, void delegate() triggered = null) {
|
|
this.label = label;
|
|
this.iconId = icon;
|
|
if(triggered !is null)
|
|
this.triggered ~= triggered;
|
|
version(win32_widgets) {
|
|
id = ++lastId;
|
|
mapping[id] = this;
|
|
}
|
|
}
|
|
|
|
string label;
|
|
ushort iconId;
|
|
// icon
|
|
|
|
// when it is triggered, the triggered event is fired on the window
|
|
void delegate()[] triggered;
|
|
}
|
|
|
|
/*
|
|
plan:
|
|
keyboard accelerators
|
|
|
|
* menus (and popups and tooltips)
|
|
* status bar
|
|
* toolbars and buttons
|
|
|
|
sortable table view
|
|
|
|
maybe notification area icons
|
|
basic clipboard
|
|
|
|
* radio box
|
|
splitter
|
|
toggle buttons (optionally mutually exclusive, like in Paint)
|
|
label, rich text display, multi line plain text (selectable)
|
|
* fieldset
|
|
* nestable grid layout
|
|
single line text input
|
|
* multi line text input
|
|
slider
|
|
spinner
|
|
list box
|
|
drop down
|
|
combo box
|
|
auto complete box
|
|
* progress bar
|
|
|
|
terminal window/widget (on unix it might even be a pty but really idk)
|
|
|
|
ok button
|
|
cancel button
|
|
|
|
keyboard hotkeys
|
|
|
|
scroll widget
|
|
|
|
event redirections and network transparency
|
|
script integration
|
|
*/
|
|
|
|
|
|
/*
|
|
MENUS
|
|
|
|
auto bar = new MenuBar(window);
|
|
window.menu = bar;
|
|
|
|
auto fileMenu = bar.addItem(new Menu("&File"));
|
|
fileMenu.addItem(new MenuItem("&Exit"));
|
|
|
|
|
|
EVENTS
|
|
|
|
For controls, you should usually use "triggered" rather than "click", etc., because
|
|
triggered handles both keyboard (focus and press as well as hotkeys) and mouse activation.
|
|
This is the case on menus and pushbuttons.
|
|
|
|
"click", on the other hand, currently only fires when it is literally clicked by the mouse.
|
|
*/
|
|
|
|
|
|
/*
|
|
enum LinePreference {
|
|
AlwaysOnOwnLine, // always on its own line
|
|
PreferOwnLine, // it will always start a new line, and if max width <= line width, it will expand all the way
|
|
PreferToShareLine, // does not force new line, and if the next child likes to share too, they will div it up evenly. otherwise, it will expand as much as it can
|
|
}
|
|
*/
|
|
|
|
mixin template Padding(string code) {
|
|
override int paddingLeft() { return mixin(code);}
|
|
override int paddingRight() { return mixin(code);}
|
|
override int paddingTop() { return mixin(code);}
|
|
override int paddingBottom() { return mixin(code);}
|
|
}
|
|
|
|
mixin template Margin(string code) {
|
|
override int marginLeft() { return mixin(code);}
|
|
override int marginRight() { return mixin(code);}
|
|
override int marginTop() { return mixin(code);}
|
|
override int marginBottom() { return mixin(code);}
|
|
}
|
|
|
|
|
|
mixin template LayoutInfo() {
|
|
int minWidth() { return 0; }
|
|
int minHeight() {
|
|
// default widgets have a vertical layout, therefore the minimum height is the sum of the contents
|
|
int sum = 0;
|
|
foreach(child; children) {
|
|
sum += child.minHeight();
|
|
sum += child.marginTop();
|
|
}
|
|
|
|
return sum;
|
|
}
|
|
int maxWidth() { return int.max; }
|
|
int maxHeight() { return int.max; }
|
|
int widthStretchiness() { return 1; }
|
|
int heightStretchiness() { return 1; }
|
|
|
|
int marginLeft() { return 0; }
|
|
int marginRight() { return 0; }
|
|
int marginTop() { return 0; }
|
|
int marginBottom() { return 0; }
|
|
int paddingLeft() { return 0; }
|
|
int paddingRight() { return 0; }
|
|
int paddingTop() { return 0; }
|
|
int paddingBottom() { return 0; }
|
|
//LinePreference linePreference() { return LinePreference.PreferOwnLine; }
|
|
|
|
void recomputeChildLayout() {
|
|
.recomputeChildLayout!"height"(this);
|
|
}
|
|
}
|
|
|
|
void recomputeChildLayout(string relevantMeasure)(Widget parent) {
|
|
enum calcingV = relevantMeasure == "height";
|
|
|
|
parent.registerMovement();
|
|
|
|
if(parent.children.length == 0)
|
|
return;
|
|
|
|
enum firstThingy = relevantMeasure == "height" ? "Top" : "Left";
|
|
enum secondThingy = relevantMeasure == "height" ? "Bottom" : "Right";
|
|
|
|
// my own width and height should already be set by the caller of this function...
|
|
int spaceRemaining = mixin("parent." ~ relevantMeasure) -
|
|
mixin("parent.padding"~firstThingy~"()") -
|
|
mixin("parent.padding"~secondThingy~"()");
|
|
|
|
int stretchinessSum;
|
|
int lastMargin = 0;
|
|
foreach(child; parent.children) {
|
|
static if(calcingV) {
|
|
child.width = parent.width -
|
|
mixin("child.margin"~firstThingy~"()") -
|
|
mixin("child.margin"~secondThingy~"()") -
|
|
mixin("parent.padding"~firstThingy~"()") -
|
|
mixin("parent.padding"~secondThingy~"()");
|
|
if(child.width < 0)
|
|
child.width = 0;
|
|
if(child.width > child.maxWidth())
|
|
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 > child.maxHeight())
|
|
child.height = child.maxHeight();
|
|
child.width = child.minWidth();
|
|
}
|
|
|
|
spaceRemaining -= mixin("child." ~ relevantMeasure);
|
|
|
|
int thisMargin = mymax(lastMargin, mixin("child.margin"~firstThingy~"()"));
|
|
auto margin = mixin("child.margin" ~ secondThingy ~ "()");
|
|
lastMargin = margin;
|
|
spaceRemaining -= thisMargin + margin;
|
|
stretchinessSum += mixin("child." ~ relevantMeasure ~ "Stretchiness()");
|
|
}
|
|
|
|
|
|
while(spaceRemaining > 0 && stretchinessSum) {
|
|
//import std.stdio; writeln("str ", stretchinessSum);
|
|
auto spacePerChild = spaceRemaining / stretchinessSum;
|
|
if(spacePerChild <= 0)
|
|
break;
|
|
int previousSpaceRemaining = spaceRemaining;
|
|
stretchinessSum = 0;
|
|
foreach(child; parent.children) {
|
|
static if(calcingV)
|
|
auto maximum = child.maxHeight();
|
|
else
|
|
auto maximum = child.maxWidth();
|
|
|
|
if(mixin("child." ~ relevantMeasure) >= maximum) {
|
|
auto adj = mixin("child." ~ relevantMeasure) - maximum;
|
|
mixin("child." ~ relevantMeasure) -= adj;
|
|
spaceRemaining += adj;
|
|
continue;
|
|
}
|
|
auto spaceAdjustment = spacePerChild * mixin("child." ~ relevantMeasure ~ "Stretchiness()");
|
|
mixin("child." ~ relevantMeasure) += spaceAdjustment;
|
|
spaceRemaining -= spaceAdjustment;
|
|
if(mixin("child." ~ relevantMeasure) > maximum) {
|
|
auto diff = mixin("child." ~ relevantMeasure) - maximum;
|
|
mixin("child." ~ relevantMeasure) -= diff;
|
|
spaceRemaining += diff;
|
|
} else if(mixin("child." ~ relevantMeasure) < maximum) {
|
|
stretchinessSum += mixin("child." ~ relevantMeasure ~ "Stretchiness()");
|
|
}
|
|
}
|
|
|
|
if(spaceRemaining == previousSpaceRemaining)
|
|
break; // apparently nothing more we can do
|
|
}
|
|
|
|
lastMargin = 0;
|
|
int currentPos = mixin("parent.padding"~firstThingy~"()");
|
|
foreach(child; parent.children) {
|
|
auto margin = mixin("child.margin" ~ secondThingy ~ "()");
|
|
int thisMargin = mymax(lastMargin, mixin("child.margin"~firstThingy~"()"));
|
|
currentPos += thisMargin;
|
|
static if(calcingV) {
|
|
child.x = parent.paddingLeft() + child.marginLeft();
|
|
child.y = currentPos;
|
|
} else {
|
|
child.x = currentPos;
|
|
child.y = parent.paddingTop() + child.marginTop();
|
|
|
|
}
|
|
currentPos += mixin("child." ~ relevantMeasure);
|
|
currentPos += margin;
|
|
lastMargin = margin;
|
|
|
|
child.recomputeChildLayout();
|
|
}
|
|
}
|
|
|
|
int mymax(int a, int b) { return a > b ? a : b; }
|
|
|
|
/+
|
|
mixin template StyleInfo(string windowType) {
|
|
version(win32_theming)
|
|
HTHEME theme;
|
|
/* ok we need to:
|
|
open theme
|
|
close theme (when it is all done)
|
|
draw background
|
|
get font
|
|
respond to theme changed messages
|
|
*/
|
|
}
|
|
+/
|
|
|
|
// OK so we need to make getting at the native window stuff possible in simpledisplay.d
|
|
// and here, it must be integrable with the layout, the event system, and not be painted over.
|
|
version(win32_widgets) {
|
|
extern(Windows)
|
|
int HookedWndProc(HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam) nothrow {
|
|
//import std.stdio; try { writeln(iMessage); } catch(Exception e) {};
|
|
if(auto te = hWnd in Widget.nativeMapping) {
|
|
if(iMessage == WM_SETFOCUS) {
|
|
auto lol = *te;
|
|
while(lol !is null && lol.implicitlyCreated)
|
|
lol = lol.parent;
|
|
(*te).parentWindow.focusedWidget = lol;
|
|
}
|
|
|
|
|
|
|
|
if(iMessage == WM_CTLCOLORBTN || iMessage == WM_CTLCOLORSTATIC) {
|
|
SetBkMode(cast(HDC) wParam, TRANSPARENT);
|
|
return cast(typeof(return))
|
|
//GetStockObject(NULL_BRUSH);
|
|
// this is the window background color...
|
|
GetSysColorBrush(COLOR_3DFACE);
|
|
}
|
|
|
|
|
|
auto pos = getChildPositionRelativeToParentOrigin(*te);
|
|
lastDefaultPrevented = false;
|
|
// try {import std.stdio; writeln(typeid(*te)); } catch(Exception e) {}
|
|
if(SimpleWindow.triggerEvents(hWnd, iMessage, wParam, lParam, pos[0], pos[1], (*te).parentWindow.win) || !lastDefaultPrevented)
|
|
return CallWindowProcW((*te).originalWindowProcedure, hWnd, iMessage, wParam, lParam);
|
|
else {
|
|
// it was something we recognized, should only call the window procedure if the default was not prevented
|
|
}
|
|
return 0;
|
|
}
|
|
assert(0, "shouldn't be receiving messages for this window....");
|
|
//import std.conv;
|
|
//assert(0, to!string(hWnd) ~ " :: " ~ to!string(TextEdit.nativeMapping)); // not supposed to happen
|
|
}
|
|
|
|
void createWin32Window(Widget p, string className, string windowText, DWORD style, DWORD extStyle = 0) {
|
|
assert(p.parentWindow !is null);
|
|
assert(p.parentWindow.win.impl.hwnd !is null);
|
|
|
|
HWND phwnd;
|
|
if(p.parent !is null && p.parent.hwnd !is null)
|
|
phwnd = p.parent.hwnd;
|
|
else
|
|
phwnd = p.parentWindow.win.impl.hwnd;
|
|
|
|
assert(phwnd !is null);
|
|
|
|
style |= WS_VISIBLE | WS_CHILD;
|
|
p.hwnd = CreateWindowExA(extStyle, toStringzInternal(className), toStringzInternal(windowText), style,
|
|
CW_USEDEFAULT, CW_USEDEFAULT, 100, 100,
|
|
phwnd, null, cast(HINSTANCE) GetModuleHandle(null), null);
|
|
|
|
assert(p.hwnd !is null);
|
|
|
|
Widget.nativeMapping[p.hwnd] = p;
|
|
|
|
p.originalWindowProcedure = cast(WNDPROC) SetWindowLong(p.hwnd, GWL_WNDPROC, cast(LONG) &HookedWndProc);
|
|
|
|
EnumChildWindows(p.hwnd, &childHandler, cast(LPARAM) cast(void*) p);
|
|
}
|
|
}
|
|
|
|
version(win32_widgets)
|
|
extern(Windows) BOOL childHandler(HWND hwnd, LPARAM lparam) {
|
|
if(hwnd is null || hwnd in Widget.nativeMapping)
|
|
return true;
|
|
auto parent = cast(Widget) cast(void*) lparam;
|
|
Widget p = new Widget();
|
|
p.parent = parent;
|
|
p.parentWindow = parent.parentWindow;
|
|
p.hwnd = hwnd;
|
|
p.implicitlyCreated = true;
|
|
Widget.nativeMapping[p.hwnd] = p;
|
|
p.originalWindowProcedure = cast(WNDPROC) SetWindowLong(p.hwnd, GWL_WNDPROC, cast(LONG) &HookedWndProc);
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
The way this module works is it builds on top of a SimpleWindow
|
|
from simpledisplay, OR Terminal from terminal to provide some
|
|
simple controls and such.
|
|
|
|
Non-native controls suck, but nevertheless, I'm going to do it that
|
|
way to avoid dependencies on stuff like gtk on X... and since I'll
|
|
be writing the widgets there, I might as well just use them on Windows
|
|
too.
|
|
|
|
So, by extension, this sucks. But gtkd is just too big for me.
|
|
|
|
|
|
The goal is to look kinda like Windows 95, perhaps with customizability.
|
|
Nothing too fancy, just the basics that work.
|
|
*/
|
|
class Widget {
|
|
mixin EventStuff!();
|
|
mixin LayoutInfo!();
|
|
|
|
static if(UsingSimpledisplayX11) {
|
|
// see: http://tronche.com/gui/x/xlib/appendix/b/
|
|
protected Cursor cursor;
|
|
|
|
// maybe I can do something similar cross platform
|
|
}
|
|
|
|
version(win32_widgets)
|
|
void handleWmCommand(ushort cmd, ushort id) {}
|
|
|
|
string statusTip;
|
|
// string toolTip;
|
|
// string helpText;
|
|
|
|
bool tabStop = true;
|
|
int tabOrder;
|
|
|
|
version(win32_widgets) {
|
|
static Widget[HWND] nativeMapping;
|
|
HWND hwnd;
|
|
WNDPROC originalWindowProcedure;
|
|
}
|
|
bool implicitlyCreated;
|
|
|
|
int x; // relative to the parent's origin
|
|
int y; // relative to the parent's origin
|
|
int width;
|
|
int height;
|
|
Widget[] children;
|
|
Widget parent;
|
|
|
|
void registerMovement() {
|
|
version(win32_widgets) {
|
|
if(hwnd) {
|
|
auto pos = getChildPositionRelativeToParentHwnd(this);
|
|
MoveWindow(hwnd, pos[0], pos[1], width, height, true);
|
|
}
|
|
}
|
|
}
|
|
|
|
Window parentWindow;
|
|
|
|
this(Widget parent = null) {
|
|
if(parent !is null)
|
|
parent.addChild(this);
|
|
}
|
|
|
|
bool showing = true;
|
|
void show() { showing = true; redraw(); }
|
|
void hide() { showing = false; }
|
|
|
|
void delegate(MouseEvent) handleMouseEvent;
|
|
void delegate(dchar) handleCharEvent;
|
|
void delegate(KeyEvent) handleKeyEvent;
|
|
|
|
bool dispatchMouseEvent(MouseEvent e) {
|
|
return eventBase!(MouseEvent, "handleMouseEvent")(e);
|
|
}
|
|
bool dispatchKeyEvent(KeyEvent e) {
|
|
return eventBase!(KeyEvent, "handleKeyEvent")(e);
|
|
}
|
|
bool dispatchCharEvent(dchar e) {
|
|
return eventBase!(dchar, "handleCharEvent")(e);
|
|
}
|
|
|
|
private bool eventBase(EventType, string handler)(EventType e) {
|
|
|
|
static if(is(EventType == MouseEvent)) {
|
|
/*
|
|
assert(e.x >= 0);
|
|
assert(e.y >= 0);
|
|
assert(e.x < width);
|
|
assert(e.y < height);
|
|
*/
|
|
|
|
auto child = getChildAtPosition(e.x, e.y);
|
|
if(child !is null) {
|
|
e.x -= child.x;
|
|
e.y -= child.y;
|
|
if(mixin("child." ~ handler) !is null)
|
|
mixin("child." ~ handler)(e);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
if(mixin(handler) !is null) {
|
|
mixin(handler)(e);
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void attachedToWindow(Window w) {}
|
|
void addedTo(Widget w) {}
|
|
|
|
private void newWindow(Window parent) {
|
|
parentWindow = parent;
|
|
foreach(child; children)
|
|
child.newWindow(parent);
|
|
}
|
|
|
|
protected void addChild(Widget w, int position = int.max) {
|
|
w.parent = this;
|
|
if(position == int.max || position == children.length)
|
|
children ~= w;
|
|
else {
|
|
assert(position < children.length);
|
|
children.length = children.length + 1;
|
|
for(int i = cast(int) children.length - 1; i > position; i--)
|
|
children[i] = children[i - 1];
|
|
children[position] = w;
|
|
}
|
|
|
|
w.newWindow(this.parentWindow);
|
|
|
|
w.addedTo(this);
|
|
|
|
if(parentWindow !is null) {
|
|
w.attachedToWindow(parentWindow);
|
|
parentWindow.recomputeChildLayout();
|
|
}
|
|
}
|
|
|
|
Widget getChildAtPosition(int x, int y) {
|
|
// it goes backward so the last one to show gets picked first
|
|
// might use z-index later
|
|
foreach_reverse(child; children) {
|
|
if(child.x <= x && child.y <= y
|
|
&& ((x - child.x) < child.width)
|
|
&& ((y - child.y) < child.height))
|
|
{
|
|
return child;
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
void delegate(ScreenPainter painter) paint;
|
|
|
|
ScreenPainter draw() {
|
|
int x = this.x, y = this.y;
|
|
auto parent = this.parent;
|
|
while(parent) {
|
|
x += parent.x;
|
|
y += parent.y;
|
|
parent = parent.parent;
|
|
}
|
|
|
|
auto painter = parentWindow.win.draw();
|
|
painter.originX = x;
|
|
painter.originY = y;
|
|
return painter;
|
|
}
|
|
|
|
protected void privatePaint(ScreenPainter painter, int lox, int loy) {
|
|
painter.originX = lox + x;
|
|
painter.originY = loy + y;
|
|
|
|
static if(UsingSimpledisplayX11) {
|
|
XRectangle[1] rects;
|
|
rects[0] = XRectangle(cast(short)(lox + x), cast(short)(loy + y), cast(short) width, cast(short) height);
|
|
XSetClipRectangles(XDisplayConnection.get, painter.impl.gc, 0, 0, rects.ptr, 1, 0);
|
|
} else {
|
|
version(Windows) {
|
|
auto region = CreateRectRgn(lox + x, loy + y, lox + x + width, loy + y + height);
|
|
SelectClipRgn(painter.impl.hdc, region);
|
|
DeleteObject(region);
|
|
}
|
|
}
|
|
if(paint !is null)
|
|
paint(painter);
|
|
foreach(child; children)
|
|
child.privatePaint(painter, painter.originX, painter.originY);
|
|
}
|
|
|
|
void redraw() {
|
|
if(!showing) return;
|
|
|
|
assert(parentWindow !is null);
|
|
auto ugh = this.parent;
|
|
int lox, loy;
|
|
while(ugh) {
|
|
lox += ugh.x;
|
|
loy += ugh.y;
|
|
ugh = ugh.parent;
|
|
}
|
|
auto painter = parentWindow.win.draw();
|
|
privatePaint(painter, lox, loy);
|
|
}
|
|
}
|
|
|
|
class VerticalLayout : Widget {
|
|
// intentionally blank - widget's default is vertical layout right now
|
|
this(Widget parent = null) { tabStop = false; super(parent); if(parent) this.parentWindow = parent.parentWindow; }
|
|
}
|
|
|
|
class StaticLayout : Widget {
|
|
this(Widget parent = null) { tabStop = false; super(parent); if(parent) this.parentWindow = parent.parentWindow; }
|
|
override void recomputeChildLayout() {
|
|
registerMovement();
|
|
foreach(child; children)
|
|
child.recomputeChildLayout();
|
|
}
|
|
}
|
|
|
|
class HorizontalLayout : Widget {
|
|
this(Widget parent = null) { tabStop = false; super(parent); if(parent) this.parentWindow = parent.parentWindow; }
|
|
override void recomputeChildLayout() {
|
|
.recomputeChildLayout!"width"(this);
|
|
}
|
|
|
|
override int minHeight() {
|
|
int largest = 0;
|
|
int margins = 0;
|
|
int lastMargin = 0;
|
|
foreach(child; children) {
|
|
auto mh = child.minHeight();
|
|
if(mh > largest)
|
|
largest = mh;
|
|
margins += mymax(lastMargin, child.marginTop());
|
|
lastMargin = child.marginBottom();
|
|
}
|
|
return largest + margins;
|
|
}
|
|
|
|
override int maxHeight() {
|
|
int largest = 0;
|
|
int margins = 0;
|
|
int lastMargin = 0;
|
|
foreach(child; children) {
|
|
auto mh = child.maxHeight();
|
|
if(mh > largest)
|
|
largest = mh;
|
|
margins += mymax(lastMargin, child.marginTop());
|
|
lastMargin = child.marginBottom();
|
|
}
|
|
return largest + margins;
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
class Window : Widget {
|
|
int mouseCaptureCount = 0;
|
|
Widget mouseCapturedBy;
|
|
void captureMouse(Widget byWhom) {
|
|
assert(mouseCapturedBy is null || byWhom is mouseCapturedBy);
|
|
mouseCaptureCount++;
|
|
mouseCapturedBy = byWhom;
|
|
}
|
|
void releaseMouseCapture() {
|
|
mouseCaptureCount--;
|
|
mouseCapturedBy = null;
|
|
}
|
|
|
|
static int lineHeight;
|
|
|
|
Widget focusedWidget;
|
|
|
|
SimpleWindow win;
|
|
this(int width = 500, int height = 500, string title = null) {
|
|
super(null);
|
|
|
|
win = new SimpleWindow(width, height, title, OpenGlOptions.no, Resizablity.allowResizing);
|
|
this.width = win.width;
|
|
this.height = win.height;
|
|
this.parentWindow = this;
|
|
|
|
|
|
win.windowResized = (int w, int h) {
|
|
this.width = w;
|
|
this.height = h;
|
|
recomputeChildLayout();
|
|
redraw();
|
|
};
|
|
|
|
win.setEventHandlers(
|
|
(MouseEvent e) {
|
|
dispatchMouseEvent(e);
|
|
},
|
|
(KeyEvent e) {
|
|
//import std.stdio;
|
|
//writefln("%x %s", cast(uint) e.key, e.key);
|
|
dispatchKeyEvent(e);
|
|
},
|
|
(dchar e) {
|
|
dispatchCharEvent(e);
|
|
},
|
|
);
|
|
|
|
bool skipNextChar = false;
|
|
|
|
addEventListener("char", (Widget, Event ev) {
|
|
if(skipNextChar) {
|
|
ev.preventDefault();
|
|
skipNextChar = false;
|
|
}
|
|
});
|
|
|
|
|
|
defaultEventHandlers["keydown"] = delegate void(Widget ignored, Event event) {
|
|
Widget _this = event.target;
|
|
|
|
if(event.key == Key.Tab) {
|
|
/* Window tab ordering is a recursive thingy with each group */
|
|
|
|
// FIXME inefficient
|
|
Widget[] helper(Widget p) {
|
|
Widget[] childOrdering = p.children.dup;
|
|
|
|
import std.algorithm;
|
|
sort!((a, b) => a.tabOrder < b.tabOrder)(childOrdering);
|
|
|
|
Widget[] ret;
|
|
foreach(child; childOrdering) {
|
|
if(child.tabStop)
|
|
ret ~= child;
|
|
ret ~= helper(child);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
Widget[] tabOrdering = helper(this);
|
|
|
|
Widget recipient;
|
|
|
|
if(tabOrdering.length) {
|
|
bool seenThis = false;
|
|
Widget previous;
|
|
foreach(idx, child; tabOrdering) {
|
|
if(child is focusedWidget) {
|
|
|
|
if(event.shiftKey) {
|
|
if(idx == 0)
|
|
recipient = tabOrdering[$-1];
|
|
else
|
|
recipient = tabOrdering[idx - 1];
|
|
break;
|
|
}
|
|
|
|
seenThis = true;
|
|
if(idx + 1 == tabOrdering.length) {
|
|
// we're at the end, either move to the next group
|
|
// or start back over
|
|
recipient = tabOrdering[0];
|
|
}
|
|
continue;
|
|
}
|
|
if(seenThis) {
|
|
recipient = child;
|
|
break;
|
|
}
|
|
previous = child;
|
|
}
|
|
}
|
|
|
|
if(recipient !is null) {
|
|
// import std.stdio; writeln(typeid(recipient));
|
|
version(win32_widgets) {
|
|
if(recipient.hwnd !is null)
|
|
SetFocus(recipient.hwnd);
|
|
} else {
|
|
focusedWidget = recipient;
|
|
}
|
|
|
|
skipNextChar = true;
|
|
}
|
|
}
|
|
};
|
|
|
|
|
|
if(lineHeight == 0) {
|
|
auto painter = win.draw();
|
|
lineHeight = painter.fontHeight() * 5 / 4;
|
|
}
|
|
|
|
version(win32_widgets) {
|
|
this.paint = (ScreenPainter painter) {
|
|
/*
|
|
RECT rect;
|
|
rect.right = this.width;
|
|
rect.bottom = this.height;
|
|
DrawThemeBackground(theme, painter.impl.hdc, 4, 1, &rect, null);
|
|
*/
|
|
// 3dface is used as window backgrounds by Windows too, so that's why I'm using it here
|
|
auto b = SelectObject(painter.impl.hdc, GetSysColorBrush(COLOR_3DFACE));
|
|
auto p = SelectObject(painter.impl.hdc, GetStockObject(NULL_PEN));
|
|
// since the pen is null, to fill the whole space, we need the +1 on both.
|
|
gdi.Rectangle(painter.impl.hdc, 0, 0, this.width + 1, this.height + 1);
|
|
SelectObject(painter.impl.hdc, p);
|
|
SelectObject(painter.impl.hdc, b);
|
|
};
|
|
}
|
|
else
|
|
this.paint = (ScreenPainter painter) {
|
|
painter.fillColor = windowBackgroundColor;
|
|
painter.outlineColor = windowBackgroundColor;
|
|
painter.drawRectangle(Point(0, 0), this.width, this.height);
|
|
};
|
|
}
|
|
|
|
void close() {
|
|
win.close();
|
|
}
|
|
|
|
override bool dispatchKeyEvent(KeyEvent ev) {
|
|
if(focusedWidget) {
|
|
auto event = new Event(ev.pressed ? "keydown" : "keyup", focusedWidget);
|
|
event.character = ev.character;
|
|
event.key = ev.key;
|
|
event.shiftKey = (ev.modifierState & ModifierState.shift) ? true : false;
|
|
event.dispatch();
|
|
}
|
|
return super.dispatchKeyEvent(ev);
|
|
}
|
|
|
|
override bool dispatchCharEvent(dchar ch) {
|
|
if(focusedWidget) {
|
|
auto event = new Event("char", focusedWidget);
|
|
event.character = ch;
|
|
event.dispatch();
|
|
}
|
|
return super.dispatchCharEvent(ch);
|
|
}
|
|
|
|
Widget mouseLastOver;
|
|
Widget mouseLastDownOn;
|
|
override bool dispatchMouseEvent(MouseEvent ev) {
|
|
auto eleR = widgetAtPoint(this, ev.x, ev.y);
|
|
auto ele = eleR.widget;
|
|
|
|
if(mouseCapturedBy !is null) {
|
|
if(ele !is mouseCapturedBy && !mouseCapturedBy.isAParentOf(ele))
|
|
ele = this;
|
|
}
|
|
|
|
if(ev.type == 1) {
|
|
mouseLastDownOn = ele;
|
|
auto event = new Event("mousedown", ele);
|
|
event.button = ev.button;
|
|
event.state = ev.modifierState;
|
|
event.clientX = eleR.x;
|
|
event.clientY = eleR.y;
|
|
event.dispatch();
|
|
} else if(ev.type == 2) {
|
|
auto event = new Event("mouseup", ele);
|
|
event.button = ev.button;
|
|
event.clientX = eleR.x;
|
|
event.clientY = eleR.y;
|
|
event.state = ev.modifierState;
|
|
event.dispatch();
|
|
if(mouseLastDownOn is ele) {
|
|
event = new Event("click", ele);
|
|
event.clientX = eleR.x;
|
|
event.clientY = eleR.y;
|
|
event.button = ev.button;
|
|
event.dispatch();
|
|
}
|
|
} else if(ev.type == 0) {
|
|
// motion
|
|
Event event = new Event("mousemove", ele);
|
|
event.state = ev.modifierState;
|
|
event.clientX = eleR.x;
|
|
event.clientY = eleR.y;
|
|
event.dispatch();
|
|
|
|
if(mouseLastOver !is ele) {
|
|
if(ele !is null) {
|
|
if(!isAParentOf(ele, mouseLastOver)) {
|
|
event = new Event("mouseenter", ele);
|
|
event.relatedTarget = mouseLastOver;
|
|
event.sendDirectly();
|
|
|
|
static if(UsingSimpledisplayX11)
|
|
XDefineCursor(XDisplayConnection.get(), ele.parentWindow.win.impl.window, ele.cursor);
|
|
}
|
|
}
|
|
|
|
if(mouseLastOver !is null) {
|
|
if(!isAParentOf(mouseLastOver, ele)) {
|
|
event = new Event("mouseleave", mouseLastOver);
|
|
event.relatedTarget = ele;
|
|
event.sendDirectly();
|
|
}
|
|
}
|
|
|
|
if(ele !is null) {
|
|
event = new Event("mouseover", ele);
|
|
event.relatedTarget = mouseLastOver;
|
|
event.dispatch();
|
|
}
|
|
|
|
if(mouseLastOver !is null) {
|
|
event = new Event("mouseout", mouseLastOver);
|
|
event.relatedTarget = ele;
|
|
event.dispatch();
|
|
}
|
|
|
|
mouseLastOver = ele;
|
|
}
|
|
}
|
|
|
|
return super.dispatchMouseEvent(ev);
|
|
}
|
|
|
|
void loop() {
|
|
recomputeChildLayout();
|
|
redraw();
|
|
win.eventLoop(0);
|
|
}
|
|
}
|
|
|
|
class MainWindow : Window {
|
|
this(string title = null) {
|
|
super(500, 500, title);
|
|
|
|
defaultEventHandlers["mouseover"] = delegate void(Widget _this, Event event) {
|
|
if(this.statusBar !is null && event.target.statusTip.length)
|
|
this.statusBar.parts[0].content = event.target.statusTip;
|
|
else if(this.statusBar !is null && _this.statusTip.length)
|
|
this.statusBar.parts[0].content = _this.statusTip; // ~ " " ~ event.target.toString();
|
|
};
|
|
|
|
version(win32_widgets)
|
|
win.handleNativeEvent = delegate int(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) {
|
|
if(hwnd !is this.win.impl.hwnd)
|
|
return 1; // we don't care...
|
|
switch(msg) {
|
|
case WM_COMMAND:
|
|
switch(HIWORD(wParam)) {
|
|
case 0:
|
|
// case BN_CLICKED: aka 0
|
|
case 1:
|
|
auto idm = LOWORD(wParam);
|
|
if(auto item = idm in Action.mapping) {
|
|
foreach(handler; (*item).triggered)
|
|
handler();
|
|
/*
|
|
auto event = new Event("triggered", *item);
|
|
event.button = idm;
|
|
event.dispatch();
|
|
*/
|
|
} else {
|
|
auto handle = cast(HWND) lParam;
|
|
if(auto widgetp = handle in Widget.nativeMapping) {
|
|
(*widgetp).handleWmCommand(HIWORD(wParam), LOWORD(wParam));
|
|
}
|
|
}
|
|
break;
|
|
default:
|
|
return 1;
|
|
}
|
|
break;
|
|
default: return 1; // not handled, pass it on
|
|
}
|
|
return 0;
|
|
};
|
|
|
|
_clientArea = new Widget();
|
|
_clientArea.x = 0;
|
|
_clientArea.y = 0;
|
|
_clientArea.width = this.width;
|
|
_clientArea.height = this.height;
|
|
_clientArea.tabStop = false;
|
|
|
|
super.addChild(_clientArea);
|
|
|
|
statusBar = new StatusBar(this);
|
|
}
|
|
|
|
override void addChild(Widget c, int position = int.max) {
|
|
clientArea.addChild(c, position);
|
|
}
|
|
|
|
MenuBar _menu;
|
|
MenuBar menu() { return _menu; }
|
|
MenuBar menu(MenuBar m) {
|
|
if(_menu !is null) {
|
|
// make sure it is sanely removed
|
|
// FIXME
|
|
}
|
|
|
|
_menu = m;
|
|
|
|
version(win32_widgets) {
|
|
SetMenu(parentWindow.win.impl.hwnd, m.handle);
|
|
} else {
|
|
super.addChild(m, 0);
|
|
|
|
// clientArea.y = menu.height;
|
|
// clientArea.height = this.height - menu.height;
|
|
|
|
recomputeChildLayout();
|
|
}
|
|
|
|
return _menu;
|
|
}
|
|
private Widget _clientArea;
|
|
@property Widget clientArea() { return _clientArea; }
|
|
@property void clientArea(Widget wid) {
|
|
_clientArea = wid;
|
|
}
|
|
|
|
private StatusBar _statusBar;
|
|
@property StatusBar statusBar() { return _statusBar; }
|
|
@property void statusBar(StatusBar bar) {
|
|
_statusBar = bar;
|
|
super.addChild(_statusBar);
|
|
}
|
|
|
|
@property string title() { return parentWindow.win.title; }
|
|
@property void title(string title) { parentWindow.win.title = title; }
|
|
}
|
|
|
|
/**
|
|
Toolbars are lists of buttons (typically icons) that appear under the menu.
|
|
Each button ought to correspond to a menu item.
|
|
*/
|
|
class ToolBar : Widget {
|
|
version(win32_widgets) {
|
|
private const int idealHeight;
|
|
override int minHeight() { return idealHeight; }
|
|
override int maxHeight() { return idealHeight; }
|
|
} else {
|
|
override int minHeight() { return Window.lineHeight * 3/2; }
|
|
override int maxHeight() { return Window.lineHeight * 3/2; }
|
|
}
|
|
override int heightStretchiness() { return 0; }
|
|
|
|
version(win32_widgets)
|
|
HIMAGELIST imageList;
|
|
|
|
this(Action[] actions, Widget parent = null) {
|
|
super(parent);
|
|
|
|
tabStop = false;
|
|
|
|
version(win32_widgets) {
|
|
parentWindow = parent.parentWindow;
|
|
createWin32Window(this, "ToolbarWindow32", "", 0);
|
|
|
|
imageList = ImageList_Create(
|
|
// width, height
|
|
16, 16,
|
|
ILC_COLOR16 | ILC_MASK,
|
|
16 /*numberOfButtons*/, 0);
|
|
|
|
SendMessageA(hwnd, TB_SETIMAGELIST, cast(WPARAM) 0, cast(LPARAM) imageList);
|
|
SendMessageA(hwnd, TB_LOADIMAGES, cast(WPARAM) IDB_STD_SMALL_COLOR, cast(LPARAM) HINST_COMMCTRL);
|
|
|
|
TBBUTTON[] buttons;
|
|
|
|
// FIXME: I_IMAGENONE is if here is no icon
|
|
foreach(action; actions)
|
|
buttons ~= TBBUTTON(MAKELONG(cast(ushort)(action.iconId ? (action.iconId - 1) : -2 /* I_IMAGENONE */), 0), action.id, TBSTATE_ENABLED, 0, 0, 0, cast(int) toStringzInternal(action.label));
|
|
|
|
SendMessageA(hwnd, TB_BUTTONSTRUCTSIZE, cast(WPARAM)TBBUTTON.sizeof, 0);
|
|
SendMessageA(hwnd, TB_ADDBUTTONSA, cast(WPARAM) buttons.length, cast(LPARAM)buttons.ptr);
|
|
|
|
/* this seems to make it a vertical toolbar on windows xp... don't actually want that
|
|
SIZE size;
|
|
SendMessageA(hwnd, TB_GETIDEALSIZE, true, cast(LPARAM) &size);
|
|
idealHeight = size.cy;
|
|
*/
|
|
|
|
RECT rect;
|
|
GetWindowRect(hwnd, &rect);
|
|
idealHeight = rect.bottom - rect.top + 10; // the +10 is a hack since the size right now doesn't look right on a real Windows XP box
|
|
|
|
assert(idealHeight);
|
|
} else {
|
|
foreach(action; actions)
|
|
addChild(new ToolButton(action));
|
|
}
|
|
}
|
|
|
|
override void recomputeChildLayout() {
|
|
.recomputeChildLayout!"width"(this);
|
|
}
|
|
}
|
|
|
|
class ToolButton : Button {
|
|
this(string label, Widget parent = null) {
|
|
super(label, parent);
|
|
}
|
|
this(Action action, Widget parent = null) {
|
|
super(action.label, parent);
|
|
this.action = action;
|
|
|
|
version(win32_widgets) {} else {
|
|
defaultEventHandlers["click"] = (Widget _this, Event event) {
|
|
foreach(handler; action.triggered)
|
|
handler();
|
|
};
|
|
|
|
paint = (ScreenPainter painter) {
|
|
painter.outlineColor = windowBackgroundColor;
|
|
if(isHovering) {
|
|
painter.fillColor = lighten(windowBackgroundColor, 0.8);
|
|
} else {
|
|
painter.fillColor = windowBackgroundColor;
|
|
}
|
|
|
|
painter.drawRectangle(Point(1, 1), width, height);
|
|
painter.outlineColor = Color.black;
|
|
painter.drawText(Point(0, 0), action.label, Point(width, height), TextAlignment.Center | TextAlignment.VerticalCenter);
|
|
};
|
|
}
|
|
}
|
|
|
|
Action action;
|
|
|
|
override int maxWidth() { return 40; }
|
|
}
|
|
|
|
|
|
class MenuBar : Widget {
|
|
MenuItem[] items;
|
|
|
|
version(win32_widgets) {
|
|
HMENU handle;
|
|
this(Widget parent = null) {
|
|
super(parent);
|
|
|
|
handle = CreateMenu();
|
|
}
|
|
} else {
|
|
this(Widget parent = null) {
|
|
super(parent);
|
|
this.paint = (ScreenPainter painter) {
|
|
painter.outlineColor = Color.black;
|
|
painter.fillColor = Color.transparent;
|
|
painter.drawRectangle(Point(0, 0), width, height);
|
|
};
|
|
}
|
|
}
|
|
|
|
MenuItem addItem(MenuItem item) {
|
|
this.addChild(item);
|
|
items ~= item;
|
|
version(win32_widgets) {
|
|
AppendMenuA(handle, MF_STRING, item.action is null ? 9000 : item.action.id, toStringzInternal(item.label)); // XXX
|
|
}
|
|
return item;
|
|
}
|
|
|
|
Menu addItem(Menu item) {
|
|
auto mbItem = new MenuItem(item.label, this.parentWindow);
|
|
|
|
addChild(mbItem);
|
|
items ~= mbItem;
|
|
|
|
version(win32_widgets) {
|
|
AppendMenuA(handle, MF_STRING | MF_POPUP, cast(UINT) item.handle, toStringzInternal(item.label)); // XXX
|
|
} else {
|
|
mbItem.defaultEventHandlers["click"] = (Widget e, Event ev) {
|
|
item.parentWindow = e.parentWindow;
|
|
item.popup(mbItem);
|
|
};
|
|
}
|
|
|
|
return item;
|
|
}
|
|
|
|
override void recomputeChildLayout() {
|
|
.recomputeChildLayout!"width"(this);
|
|
}
|
|
|
|
override int maxHeight() { return Window.lineHeight + 4; }
|
|
override int minHeight() { return Window.lineHeight + 4; }
|
|
|
|
}
|
|
|
|
|
|
/**
|
|
Status bars appear at the bottom of a MainWindow.
|
|
They are made out of Parts, with a width and content.
|
|
|
|
They can have multiple parts or be in simple mode. FIXME: implement
|
|
|
|
|
|
sb.parts[0].content = "Status bar text!";
|
|
*/
|
|
class StatusBar : Widget {
|
|
private Part[] partsArray;
|
|
struct Parts {
|
|
@disable this();
|
|
this(StatusBar owner) { this.owner = owner; }
|
|
//@disable this(this);
|
|
@property int length() { return cast(int) owner.partsArray.length; }
|
|
private StatusBar owner;
|
|
private this(StatusBar owner, Part[] parts) {
|
|
this.owner.partsArray = parts;
|
|
this.owner = owner;
|
|
}
|
|
Part opIndex(int p) {
|
|
if(owner.partsArray.length == 0)
|
|
this ~= new StatusBar.Part(300);
|
|
return owner.partsArray[p];
|
|
}
|
|
|
|
Part opOpAssign(string op : "~" )(Part p) {
|
|
assert(owner.partsArray.length < 255);
|
|
p.owner = this.owner;
|
|
p.idx = cast(int) owner.partsArray.length;
|
|
owner.partsArray ~= p;
|
|
version(win32_widgets) {
|
|
int[256] pos;
|
|
int cpos = 0;
|
|
foreach(idx, part; owner.partsArray) {
|
|
if(part.width)
|
|
cpos += part.width;
|
|
else
|
|
cpos += 100;
|
|
|
|
if(idx + 1 == owner.partsArray.length)
|
|
pos[idx] = -1;
|
|
else
|
|
pos[idx] = cpos;
|
|
}
|
|
SendMessageA(owner.hwnd, WM_USER + 4 /*SB_SETPARTS*/, owner.partsArray.length, cast(int) pos.ptr);
|
|
} else {
|
|
owner.redraw();
|
|
}
|
|
|
|
return p;
|
|
}
|
|
}
|
|
|
|
private Parts _parts;
|
|
@property Parts parts() {
|
|
return _parts;
|
|
}
|
|
|
|
static class Part {
|
|
int width;
|
|
StatusBar owner;
|
|
|
|
this(int w = 100) { width = w; }
|
|
|
|
private int idx;
|
|
private string _content;
|
|
@property string content() { return _content; }
|
|
@property void content(string s) {
|
|
version(win32_widgets) {
|
|
_content = s;
|
|
SendMessageA(owner.hwnd, SB_SETTEXT, idx, cast(LPARAM) toStringzInternal(s));
|
|
} else {
|
|
if(_content != s) {
|
|
_content = s;
|
|
owner.redraw();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
string simpleModeContent;
|
|
bool inSimpleMode;
|
|
|
|
|
|
this(Widget parent = null) {
|
|
super(null); // FIXME
|
|
_parts = Parts(this);
|
|
tabStop = false;
|
|
version(win32_widgets) {
|
|
parentWindow = parent.parentWindow;
|
|
createWin32Window(this, "msctls_statusbar32", "", 0);
|
|
|
|
RECT rect;
|
|
GetWindowRect(hwnd, &rect);
|
|
idealHeight = rect.bottom - rect.top;
|
|
assert(idealHeight);
|
|
} else {
|
|
this.paint = (ScreenPainter painter) {
|
|
painter.outlineColor = Color.black;
|
|
painter.fillColor = windowBackgroundColor;
|
|
painter.drawRectangle(Point(0, 0), width, height);
|
|
int cpos = 4;
|
|
foreach(part; this.partsArray) {
|
|
painter.drawText(Point(cpos, 0), part.content, Point(width, height));
|
|
cpos += part.width ? part.width : 100;
|
|
}
|
|
};
|
|
}
|
|
}
|
|
|
|
version(win32_widgets) {
|
|
private const int idealHeight;
|
|
override int maxHeight() { return idealHeight; }
|
|
override int minHeight() { return idealHeight; }
|
|
} else {
|
|
override int maxHeight() { return Window.lineHeight + 4; }
|
|
override int minHeight() { return Window.lineHeight + 4; }
|
|
}
|
|
}
|
|
|
|
/// Displays an in-progress indicator without known values
|
|
version(none)
|
|
class IndefiniteProgressBar : Widget {
|
|
version(win32_widgets)
|
|
this(Widget parent = null) {
|
|
super(parent);
|
|
parentWindow = parent.parentWindow;
|
|
createWin32Window(this, "msctls_progress32", "", 8 /* PBS_MARQUEE */);
|
|
tabStop = false;
|
|
}
|
|
override int minHeight() { return 10; }
|
|
}
|
|
|
|
/// A progress bar with a known endpoint and completion amount
|
|
class ProgressBar : Widget {
|
|
version(win32_widgets)
|
|
this(Widget parent = null) {
|
|
super(parent);
|
|
parentWindow = parent.parentWindow;
|
|
createWin32Window(this, "msctls_progress32", "", 0);
|
|
tabStop = false;
|
|
}
|
|
else {
|
|
this(Widget parent = null) {
|
|
super(parent);
|
|
max = 100;
|
|
step = 10;
|
|
paint = (ScreenPainter painter) {
|
|
painter.fillColor = windowBackgroundColor;
|
|
painter.drawRectangle(Point(0, 0), width, height);
|
|
painter.fillColor = Color.blue;
|
|
painter.drawRectangle(Point(0, 0), width * current / max, height);
|
|
};
|
|
}
|
|
|
|
int current;
|
|
int max;
|
|
int step;
|
|
}
|
|
|
|
void advanceOneStep() {
|
|
version(win32_widgets)
|
|
SendMessageA(hwnd, PBM_STEPIT, 0, 0);
|
|
else
|
|
addToPosition(step);
|
|
}
|
|
|
|
void setStepIncrement(int increment) {
|
|
version(win32_widgets)
|
|
SendMessageA(hwnd, PBM_SETSTEP, increment, 0);
|
|
else
|
|
step = increment;
|
|
}
|
|
|
|
void addToPosition(int amount) {
|
|
version(win32_widgets)
|
|
SendMessageA(hwnd, PBM_DELTAPOS, amount, 0);
|
|
else {
|
|
setPosition(current + amount);
|
|
}
|
|
}
|
|
|
|
void setPosition(int pos) {
|
|
version(win32_widgets)
|
|
SendMessageA(hwnd, PBM_SETPOS, pos, 0);
|
|
else {
|
|
current = pos;
|
|
if(current > max)
|
|
current = max;
|
|
redraw();
|
|
}
|
|
}
|
|
|
|
void setRange(ushort min, ushort max) {
|
|
version(win32_widgets)
|
|
SendMessageA(hwnd, PBM_SETRANGE, 0, MAKELONG(min, max));
|
|
else {
|
|
this.max = max;
|
|
}
|
|
}
|
|
|
|
override int minHeight() { return 10; }
|
|
}
|
|
|
|
class Fieldset : Widget {
|
|
// FIXME: on Windows,it doesn't draw the background on the label
|
|
// on X, it doesn't fix the clipping rectangle for it
|
|
version(win32_widgets)
|
|
override int paddingTop() { return Window.lineHeight; }
|
|
else
|
|
override int paddingTop() { return Window.lineHeight / 2 + 2; }
|
|
override int paddingBottom() { return 6; }
|
|
override int paddingLeft() { return 6; }
|
|
override int paddingRight() { return 6; }
|
|
mixin Margin!q{ Window.lineHeight / 2 + 2 };
|
|
|
|
string legend;
|
|
|
|
version(win32_widgets)
|
|
this(string legend, Widget parent = null) {
|
|
super(parent);
|
|
this.legend = legend;
|
|
parentWindow = parent.parentWindow;
|
|
createWin32Window(this, "button", legend, BS_GROUPBOX);
|
|
}
|
|
else
|
|
this(string legend, Widget parent = null) {
|
|
super(parent);
|
|
this.legend = legend;
|
|
parentWindow = parent.parentWindow;
|
|
this.paint = (ScreenPainter painter) {
|
|
painter.fillColor = Color.transparent;
|
|
painter.pen = Pen(Color.black, 1);
|
|
painter.drawRectangle(Point(0, 0), width, height);
|
|
|
|
auto tx = painter.textSize(legend);
|
|
painter.outlineColor = Color.transparent;
|
|
|
|
static if(UsingSimpledisplayX11) {
|
|
painter.fillColor = windowBackgroundColor;
|
|
painter.drawRectangle(Point(8, -tx.height/2), tx.width, tx.height);
|
|
} else version(Windows) {
|
|
auto b = SelectObject(painter.impl.hdc, GetSysColorBrush(COLOR_3DFACE));
|
|
painter.drawRectangle(Point(8, -tx.height/2), tx.width, tx.height);
|
|
SelectObject(painter.impl.hdc, b);
|
|
} else static assert(0);
|
|
painter.outlineColor = Color.black;
|
|
painter.drawText(Point(8, -tx.height / 2), legend);
|
|
};
|
|
}
|
|
|
|
override int maxHeight() {
|
|
auto m = paddingTop() + paddingBottom();
|
|
foreach(child; children) {
|
|
m += child.maxHeight();
|
|
m += child.marginBottom();
|
|
m += child.marginTop();
|
|
}
|
|
return m + 6;
|
|
}
|
|
|
|
override int minHeight() {
|
|
return super.minHeight() + Window.lineHeight + 4;
|
|
}
|
|
}
|
|
|
|
class Menu : Widget {
|
|
void remove() {
|
|
foreach(i, child; parentWindow.children)
|
|
if(child is this) {
|
|
parentWindow.children = parentWindow.children[0 .. i] ~ parentWindow.children[i + 1 .. $];
|
|
break;
|
|
}
|
|
parentWindow.redraw();
|
|
|
|
parentWindow.removeEventListener("mousedown", &remove);
|
|
parentWindow.releaseMouseCapture();
|
|
}
|
|
|
|
version(win32_widgets) {} else
|
|
void popup(Widget parent) {
|
|
assert(parentWindow !is null);
|
|
auto pos = getChildPositionRelativeToParentOrigin(parent);
|
|
this.x = pos[0];
|
|
this.y = pos[1] + parent.height;
|
|
this.width = 150;
|
|
if(this.children.length)
|
|
this.height = cast(int) this.children.length * this.children[0].maxHeight();
|
|
else
|
|
this.height = 4;
|
|
this.recomputeChildLayout();
|
|
|
|
this.paint = (ScreenPainter painter) {
|
|
painter.outlineColor = Color.black;
|
|
painter.fillColor = lighten(windowBackgroundColor, 0.8);
|
|
painter.drawRectangle(Point(0, 0), width, height);
|
|
};
|
|
|
|
parentWindow.children ~= this;
|
|
|
|
parentWindow.addEventListener("mousedown", &remove);
|
|
|
|
defaultEventHandlers["mousedown"] = (Widget _this, Event ev) {
|
|
ev.stopPropagation();
|
|
};
|
|
|
|
parentWindow.captureMouse(this);
|
|
|
|
foreach(child; children)
|
|
child.parentWindow = this.parentWindow;
|
|
|
|
this.show();
|
|
}
|
|
|
|
MenuItem[] items;
|
|
|
|
MenuItem addItem(MenuItem item) {
|
|
addChild(item);
|
|
items ~= item;
|
|
version(win32_widgets) {
|
|
AppendMenuA(handle, MF_STRING, item.action is null ? 9000 : item.action.id, toStringzInternal(item.label)); // XXX
|
|
}
|
|
return item;
|
|
}
|
|
|
|
string label;
|
|
|
|
version(win32_widgets) {
|
|
HMENU handle;
|
|
this(string label, Widget parent = null) {
|
|
super(parent);
|
|
this.label = label;
|
|
handle = CreatePopupMenu();
|
|
}
|
|
} else {
|
|
this(string label, Widget parent = null) {
|
|
super(parent);
|
|
this.label = label;
|
|
this.paint = (ScreenPainter painter) {
|
|
painter.outlineColor = Color.black;
|
|
painter.fillColor = Color.transparent;
|
|
painter.drawRectangle(Point(0, 0), width, height);
|
|
};
|
|
}
|
|
}
|
|
|
|
override int maxHeight() { return Window.lineHeight; }
|
|
override int minHeight() { return Window.lineHeight; }
|
|
}
|
|
|
|
class MenuItem : MouseActivatedWidget {
|
|
Menu submenu;
|
|
|
|
Action action;
|
|
string label;
|
|
|
|
override int maxHeight() { return Window.lineHeight + 4; }
|
|
override int minWidth() { return Window.lineHeight * cast(int) label.length + 8; }
|
|
override int maxWidth() {
|
|
if(cast(MenuBar) parent)
|
|
return Window.lineHeight / 2 * cast(int) label.length + 8;
|
|
return int.max;
|
|
}
|
|
this(string lbl, Widget parent = null) {
|
|
super(parent);
|
|
label = lbl;
|
|
version(win32_widgets) {} else
|
|
this.paint = (ScreenPainter painter) {
|
|
if(isHovering)
|
|
painter.outlineColor = Color.blue;
|
|
else
|
|
painter.outlineColor = Color.black;
|
|
painter.drawText(Point(cast(MenuBar) this.parent ? 4 : 20, 2), label, Point(width, height), TextAlignment.Left);
|
|
};
|
|
}
|
|
|
|
this(Action action, Widget parent = null) {
|
|
assert(action !is null);
|
|
this(action.label);
|
|
this.action = action;
|
|
defaultEventHandlers["click"] = (Widget w, Event ev) {
|
|
//auto event = new Event("triggered", this);
|
|
//event.dispatch();
|
|
foreach(handler; action.triggered)
|
|
handler();
|
|
|
|
if(auto pmenu = cast(Menu) this.parent)
|
|
pmenu.remove();
|
|
};
|
|
}
|
|
}
|
|
|
|
version(win32_widgets)
|
|
class MouseActivatedWidget : Widget {
|
|
bool isChecked() {
|
|
assert(hwnd);
|
|
return SendMessageA(hwnd, BM_GETCHECK, 0, 0) == BST_CHECKED;
|
|
|
|
}
|
|
void isChecked(bool state) {
|
|
assert(hwnd);
|
|
SendMessageA(hwnd, BM_SETCHECK, state ? BST_CHECKED : BST_UNCHECKED, 0);
|
|
|
|
}
|
|
|
|
this(Widget parent = null) {
|
|
super(parent);
|
|
}
|
|
}
|
|
else
|
|
class MouseActivatedWidget : Widget {
|
|
bool isDepressed = false;
|
|
bool isHovering = false;
|
|
bool isChecked = false;
|
|
|
|
override void attachedToWindow(Window w) {
|
|
w.addEventListener("mouseup", delegate (Widget _this, Event ev) {
|
|
isDepressed = false;
|
|
});
|
|
}
|
|
|
|
this(Widget parent = null) {
|
|
super(parent);
|
|
addEventListener("mouseenter", delegate (Widget _this, Event ev) {
|
|
isHovering = true;
|
|
redraw();
|
|
});
|
|
|
|
addEventListener("mouseleave", delegate (Widget _this, Event ev) {
|
|
isHovering = false;
|
|
redraw();
|
|
});
|
|
|
|
addEventListener("mousedown", delegate (Widget _this, Event ev) {
|
|
isDepressed = true;
|
|
redraw();
|
|
});
|
|
|
|
addEventListener("mouseup", delegate (Widget _this, Event ev) {
|
|
isDepressed = false;
|
|
redraw();
|
|
});
|
|
|
|
defaultEventHandlers["click"] = (Widget w, Event ev) {
|
|
auto event = new Event("triggered", this);
|
|
event.dispatch();
|
|
};
|
|
}
|
|
}
|
|
|
|
|
|
class Checkbox : MouseActivatedWidget {
|
|
|
|
override int maxHeight() { return 16; }
|
|
override int minHeight() { return 16; }
|
|
mixin Margin!"4";
|
|
|
|
version(win32_widgets)
|
|
this(string label, Widget parent = null) {
|
|
super(parent);
|
|
parentWindow = parent.parentWindow;
|
|
createWin32Window(this, "button", label, BS_AUTOCHECKBOX);
|
|
}
|
|
else
|
|
this(string label, Widget parent = null) {
|
|
super(parent);
|
|
|
|
this.paint = (ScreenPainter painter) {
|
|
painter.outlineColor = Color.black;
|
|
painter.fillColor = Color.white;
|
|
painter.drawRectangle(Point(2, 2), height - 2, height - 2);
|
|
|
|
if(isChecked) {
|
|
painter.pen = Pen(Color.black, 2);
|
|
// I'm using height so the checkbox is square
|
|
painter.drawLine(Point(6, 6), Point(height - 4, height - 4));
|
|
painter.drawLine(Point(height-4, 6), Point(6, height - 4));
|
|
|
|
painter.pen = Pen(Color.black, 1);
|
|
}
|
|
|
|
painter.drawText(Point(height + 4, 0), label, Point(width, height), TextAlignment.Left | TextAlignment.VerticalCenter);
|
|
};
|
|
|
|
defaultEventHandlers["click"] = delegate (Widget _this, Event ev) {
|
|
isChecked = !isChecked;
|
|
|
|
auto event = new Event("change", this);
|
|
event.dispatch();
|
|
|
|
redraw();
|
|
};
|
|
}
|
|
}
|
|
|
|
class VerticalSpacer : Widget {
|
|
override int maxHeight() { return 20; }
|
|
override int minHeight() { return 20; }
|
|
this(Widget parent = null) {
|
|
super(parent);
|
|
}
|
|
}
|
|
|
|
class MutuallyExclusiveGroup {
|
|
MouseActivatedWidget[] members;
|
|
|
|
Radiobox addMember(Radiobox w) {
|
|
members ~= w;
|
|
w.group = this;
|
|
return w;
|
|
}
|
|
|
|
void uncheckOthers(Widget checked) {
|
|
foreach(member; members)
|
|
if(member !is checked) {
|
|
member.isChecked = false;
|
|
member.redraw();
|
|
}
|
|
}
|
|
}
|
|
|
|
class Radiobox : MouseActivatedWidget {
|
|
MutuallyExclusiveGroup group;
|
|
|
|
override int maxHeight() { return 16; }
|
|
override int minHeight() { return 16; }
|
|
|
|
version(win32_widgets)
|
|
this(string label, Widget parent = null) {
|
|
super(parent);
|
|
parentWindow = parent.parentWindow;
|
|
createWin32Window(this, "button", label, BS_AUTORADIOBUTTON);
|
|
}
|
|
else
|
|
this(string label, Widget parent = null) {
|
|
super(parent);
|
|
height = 16;
|
|
width = height + 4 + cast(int) label.length * 16;
|
|
|
|
this.paint = (ScreenPainter painter) {
|
|
painter.outlineColor = Color.black;
|
|
painter.fillColor = Color.white;
|
|
painter.drawEllipse(Point(2, 2), Point(height - 2, height - 2));
|
|
|
|
if(isChecked) {
|
|
painter.outlineColor = Color.black;
|
|
painter.fillColor = Color.black;
|
|
// I'm using height so the checkbox is square
|
|
painter.drawEllipse(Point(5, 5), Point(height - 5, height - 5));
|
|
}
|
|
|
|
painter.drawText(Point(height + 4, 0), label, Point(width, height), TextAlignment.Left | TextAlignment.VerticalCenter);
|
|
};
|
|
|
|
defaultEventHandlers["click"] = delegate (Widget _this, Event ev) {
|
|
isChecked = true;
|
|
|
|
if(group !is null)
|
|
group.uncheckOthers(this);
|
|
|
|
auto event = new Event("change", this);
|
|
event.dispatch();
|
|
|
|
redraw();
|
|
};
|
|
}
|
|
}
|
|
|
|
|
|
class Button : MouseActivatedWidget {
|
|
Color normalBgColor;
|
|
Color hoverBgColor;
|
|
Color depressedBgColor;
|
|
|
|
version(win32_widgets)
|
|
override void handleWmCommand(ushort cmd, ushort id) {
|
|
auto event = new Event("triggered", this);
|
|
event.dispatch();
|
|
}
|
|
|
|
version(win32_widgets) {} else
|
|
Color currentButtonColor() {
|
|
if(isHovering) {
|
|
return isDepressed ? depressedBgColor : hoverBgColor;
|
|
}
|
|
|
|
return normalBgColor;
|
|
}
|
|
|
|
version(win32_widgets)
|
|
this(string label, Widget parent = null) {
|
|
super(parent);
|
|
parentWindow = parent.parentWindow;
|
|
createWin32Window(this, "button", label, BS_PUSHBUTTON);
|
|
|
|
// FIXME: use ideal button size instead
|
|
width = 50;
|
|
height = 30;
|
|
}
|
|
else
|
|
|
|
this(string label, Widget parent = null) {
|
|
super(parent);
|
|
normalBgColor = Color(192, 192, 192);
|
|
hoverBgColor = Color(215, 215, 215);
|
|
depressedBgColor = Color(160, 160, 160);
|
|
|
|
width = 50;
|
|
height = 30;
|
|
|
|
this.paint = (ScreenPainter painter) {
|
|
painter.outlineColor = Color.black;
|
|
painter.fillColor = currentButtonColor;
|
|
painter.drawRectangle(Point(0, 0), width, height);
|
|
|
|
|
|
painter.outlineColor = (isHovering && isDepressed) ? Color(128, 128, 128) : Color.white;
|
|
painter.drawLine(Point(0, 0), Point(width, 0));
|
|
painter.drawLine(Point(0, 0), Point(0, height - 1));
|
|
|
|
painter.outlineColor = (isHovering && isDepressed) ? Color.white : Color(128, 128, 128);
|
|
painter.drawLine(Point(width - 1, 1), Point(width - 1, height - 1));
|
|
painter.drawLine(Point(1, height - 1), Point(width - 1, height - 1));
|
|
|
|
|
|
painter.outlineColor = Color.black;
|
|
painter.drawText(Point(0, 0), label, Point(width, height), TextAlignment.Center | TextAlignment.VerticalCenter);
|
|
};
|
|
}
|
|
|
|
override int minHeight() { return Window.lineHeight; }
|
|
}
|
|
|
|
int[2] getChildPositionRelativeToParentOrigin(Widget c) nothrow {
|
|
int x, y;
|
|
Widget par = c;
|
|
while(par) {
|
|
x += par.x;
|
|
y += par.y;
|
|
par = par.parent;
|
|
}
|
|
return [x, y];
|
|
}
|
|
|
|
version(win32_widgets)
|
|
int[2] getChildPositionRelativeToParentHwnd(Widget c) nothrow {
|
|
int x, y;
|
|
Widget par = c;
|
|
while(par) {
|
|
x += par.x;
|
|
y += par.y;
|
|
par = par.parent;
|
|
if(par !is null && par.hwnd !is null)
|
|
break;
|
|
}
|
|
return [x, y];
|
|
}
|
|
|
|
class TextLabel : Widget {
|
|
override int maxHeight() { return Window.lineHeight; }
|
|
override int minHeight() { return Window.lineHeight; }
|
|
|
|
string label;
|
|
this(string label, Widget parent = null) {
|
|
this.label = label;
|
|
this.tabStop = false;
|
|
super(parent);
|
|
parentWindow = parent.parentWindow;
|
|
paint = (ScreenPainter painter) {
|
|
painter.outlineColor = Color.black;
|
|
painter.drawText(Point(0, 0), this.label, Point(width,height), TextAlignment.Right);
|
|
};
|
|
}
|
|
|
|
}
|
|
|
|
class LineEdit : Widget {
|
|
version(win32_widgets)
|
|
this(Widget parent = null) {
|
|
super(parent);
|
|
parentWindow = parent.parentWindow;
|
|
createWin32Window(this, "edit", "",
|
|
0, WS_EX_CLIENTEDGE);//|WS_HSCROLL|ES_AUTOHSCROLL);
|
|
}
|
|
else
|
|
this(Widget parent = null) {
|
|
super(parent);
|
|
|
|
this.paint = (ScreenPainter painter) {
|
|
painter.fillColor = Color.white;
|
|
painter.drawRectangle(Point(0, 0), width, height);
|
|
|
|
painter.outlineColor = Color.black;
|
|
painter.drawText(Point(4, 4), content, Point(width - 4, height - 4));
|
|
};
|
|
|
|
defaultEventHandlers["click"] = delegate (Widget _this, Event ev) {
|
|
this.focus();
|
|
};
|
|
|
|
defaultEventHandlers["char"] = delegate (Widget _this, Event ev) {
|
|
content = content() ~ cast(char) ev.character;
|
|
redraw();
|
|
};
|
|
|
|
static if(UsingSimpledisplayX11)
|
|
cursor = XCreateFontCursor(XDisplayConnection.get(), 152 /* XC_xterm, a text input thingy */);
|
|
//super();
|
|
}
|
|
|
|
|
|
string _content;
|
|
@property string content() {
|
|
version(win32_widgets) {
|
|
char[4096] buffer;
|
|
|
|
// FIXME: GetWindowTextW
|
|
// FIXME: GetWindowTextLength
|
|
auto l = GetWindowTextA(hwnd, buffer.ptr, buffer.length - 1);
|
|
if(l >= 0)
|
|
_content = buffer[0 .. l].idup;
|
|
}
|
|
return _content;
|
|
}
|
|
@property void content(string s) {
|
|
_content = s;
|
|
version(win32_widgets)
|
|
SetWindowTextA(hwnd, toStringzInternal(s));
|
|
else
|
|
redraw();
|
|
}
|
|
|
|
void focus() {
|
|
assert(parentWindow !is null);
|
|
parentWindow.focusedWidget = this;
|
|
}
|
|
|
|
override int minHeight() { return Window.lineHeight; }
|
|
override int maxHeight() { return Window.lineHeight; }
|
|
override int widthStretchiness() { return 3; }
|
|
}
|
|
|
|
class TextEdit : Widget {
|
|
|
|
// FIXME
|
|
mixin ExperimentalTextComponent;
|
|
|
|
override int minHeight() { return Window.lineHeight; }
|
|
override int heightStretchiness() { return 3; }
|
|
override int widthStretchiness() { return 3; }
|
|
|
|
version(win32_widgets)
|
|
this(Widget parent = null) {
|
|
super(parent);
|
|
parentWindow = parent.parentWindow;
|
|
createWin32Window(this, "edit", "",
|
|
0|WS_VSCROLL|WS_HSCROLL|ES_MULTILINE|ES_WANTRETURN|ES_AUTOHSCROLL|ES_AUTOVSCROLL, WS_EX_CLIENTEDGE);
|
|
}
|
|
else
|
|
this(Widget parent = null) {
|
|
super(parent);
|
|
|
|
textLayout = new TextLayout(Rectangle(0, 0, width, height));
|
|
|
|
this.paint = (ScreenPainter painter) {
|
|
painter.fillColor = Color.white;
|
|
painter.drawRectangle(Point(0, 0), width, height);
|
|
|
|
textLayout.boundingBox = Rectangle(4, 4, width - 8, height - 8);
|
|
|
|
painter.outlineColor = Color.black;
|
|
// painter.drawText(Point(4, 4), content, Point(width - 4, height - 4));
|
|
|
|
textLayout.drawInto(painter);
|
|
};
|
|
|
|
caratTimer = new Timer(500, {
|
|
if(!parentWindow.win.closed && parentWindow.focusedWidget is this) {
|
|
auto painter = this.draw();
|
|
painter.pen = Pen(Color.white, 1);
|
|
painter.rasterOp = RasterOp.xor;
|
|
if(lastClick.element) {
|
|
painter.drawLine(
|
|
Point(lastClick.element.xOfIndex(lastClick.offset + 1), lastClick.element.boundingBox.top),
|
|
Point(lastClick.element.xOfIndex(lastClick.offset + 1), lastClick.element.boundingBox.bottom)
|
|
);
|
|
} else {
|
|
painter.drawLine(
|
|
Point(4, 4),
|
|
Point(4, 10)
|
|
);
|
|
}
|
|
}
|
|
});
|
|
|
|
defaultEventHandlers["click"] = delegate (Widget _this, Event ev) {
|
|
this.focus();
|
|
lastClick = textLayout.identify(ev.clientX, ev.clientY);
|
|
};
|
|
|
|
defaultEventHandlers["char"] = delegate (Widget _this, Event ev) {
|
|
textLayout.addText("" ~ cast(char) ev.character); // FIXME
|
|
redraw();
|
|
};
|
|
|
|
static if(UsingSimpledisplayX11)
|
|
cursor = XCreateFontCursor(XDisplayConnection.get(), 152 /* XC_xterm, a text input thingy */);
|
|
//super();
|
|
}
|
|
|
|
@property string content() {
|
|
version(win32_widgets) {
|
|
char[4096] buffer;
|
|
// FIXME: GetWindowTextW
|
|
// FIXME: GetWindowTextLength
|
|
auto l = GetWindowTextA(hwnd, buffer.ptr, buffer.length - 1);
|
|
if(l >= 0)
|
|
return buffer[0 .. l].idup;
|
|
else
|
|
return null;
|
|
} else {
|
|
return textLayout.getPlainText();
|
|
}
|
|
}
|
|
@property void content(string s) {
|
|
version(win32_widgets)
|
|
SetWindowTextA(hwnd, toStringzInternal(s));
|
|
else {
|
|
textLayout.clear();
|
|
textLayout.addText(s);
|
|
redraw();
|
|
}
|
|
}
|
|
|
|
void focus() {
|
|
assert(parentWindow !is null);
|
|
parentWindow.focusedWidget = this;
|
|
}
|
|
|
|
version(win32_widgets) {
|
|
|
|
} else {
|
|
Timer caratTimer;
|
|
TextLayout textLayout;
|
|
TextIdentifyResult lastClick;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
class MessageBox : Window {
|
|
this(string message) {
|
|
super(300, 100);
|
|
|
|
auto superPaint = this.paint;
|
|
this.paint = (ScreenPainter painter) {
|
|
if(superPaint)
|
|
superPaint(painter);
|
|
painter.outlineColor = Color.black;
|
|
painter.drawText(Point(0, 0), message, Point(width, height / 2), TextAlignment.Center | TextAlignment.VerticalCenter);
|
|
};
|
|
|
|
auto button = new Button("OK", this);
|
|
button. x = this.width / 2 - button.width / 2;
|
|
button.y = height - (button.height + 10);
|
|
button.addEventListener(EventType.click, () {
|
|
close();
|
|
});
|
|
|
|
button.registerMovement();
|
|
|
|
redraw();
|
|
}
|
|
|
|
// this one is all fixed position
|
|
override void recomputeChildLayout() {}
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* FIXME: this is mostly copy/pasta'd from dom.d. Would be nice to kill the duplication */
|
|
|
|
mixin template EventStuff() {
|
|
EventHandler[][string] bubblingEventHandlers;
|
|
EventHandler[][string] capturingEventHandlers;
|
|
EventHandler[string] defaultEventHandlers;
|
|
|
|
void addEventListener(string event, void delegate() handler, bool useCapture = false) {
|
|
addEventListener(event, (Widget, Event) { handler(); }, useCapture);
|
|
}
|
|
|
|
void addEventListener(string event, void delegate(Event) handler, bool useCapture = false) {
|
|
addEventListener(event, (Widget, Event e) { handler(e); }, useCapture);
|
|
}
|
|
|
|
void addEventListener(string event, EventHandler handler, bool useCapture = false) {
|
|
if(event.length > 2 && event[0..2] == "on")
|
|
event = event[2 .. $];
|
|
|
|
if(useCapture)
|
|
capturingEventHandlers[event] ~= handler;
|
|
else
|
|
bubblingEventHandlers[event] ~= handler;
|
|
}
|
|
|
|
void removeEventListener(string event, void delegate() handler, bool useCapture = false) {
|
|
removeEventListener(event, (Widget, Event) { handler(); }, useCapture);
|
|
}
|
|
|
|
void removeEventListener(string event, void delegate(Event) handler, bool useCapture = false) {
|
|
removeEventListener(event, (Widget, Event e) { handler(e); }, useCapture);
|
|
}
|
|
|
|
void removeEventListener(string event, EventHandler handler, bool useCapture = false) {
|
|
if(event.length > 2 && event[0..2] == "on")
|
|
event = event[2 .. $];
|
|
|
|
if(useCapture) {
|
|
foreach(ref evt; capturingEventHandlers[event])
|
|
if(evt is handler) evt = null;
|
|
} else {
|
|
foreach(ref evt; bubblingEventHandlers[event])
|
|
if(evt is handler) evt = null;
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
alias void delegate(Widget handlerAttachedTo, Event event) EventHandler;
|
|
|
|
enum EventType : string {
|
|
click = "click",
|
|
|
|
mouseenter = "mouseenter",
|
|
mouseleave = "mouseleave",
|
|
mousein = "mousein",
|
|
mouseout = "mouseout",
|
|
mouseup = "mouseup",
|
|
mousedown = "mousedown",
|
|
mousemove = "mousemove",
|
|
|
|
keydown = "keydown",
|
|
keyup = "keyup",
|
|
// char = "char",
|
|
|
|
focus = "focus",
|
|
blur = "blur",
|
|
|
|
triggered = "triggered",
|
|
}
|
|
|
|
class Event {
|
|
this(string eventName, Widget target) {
|
|
this.eventName = eventName;
|
|
this.srcElement = target;
|
|
}
|
|
|
|
/// Prevents the default event handler (if there is one) from being called
|
|
void preventDefault() {
|
|
lastDefaultPrevented = true;
|
|
defaultPrevented = true;
|
|
}
|
|
|
|
/// Stops the event propagation immediately.
|
|
void stopPropagation() {
|
|
propagationStopped = true;
|
|
}
|
|
|
|
private bool defaultPrevented;
|
|
private bool propagationStopped;
|
|
private string eventName;
|
|
|
|
Widget srcElement;
|
|
alias srcElement target;
|
|
|
|
Widget relatedTarget;
|
|
|
|
int clientX;
|
|
int clientY;
|
|
|
|
int button;
|
|
Key key;
|
|
dchar character;
|
|
|
|
int state;
|
|
|
|
bool shiftKey;
|
|
|
|
private bool isBubbling;
|
|
|
|
/// this sends it only to the target. If you want propagation, use dispatch() instead.
|
|
void sendDirectly() {
|
|
if(srcElement is null)
|
|
return;
|
|
|
|
auto e = srcElement;
|
|
|
|
if(eventName in e.bubblingEventHandlers)
|
|
foreach(handler; e.bubblingEventHandlers[eventName])
|
|
handler(e, this);
|
|
|
|
if(!defaultPrevented)
|
|
if(eventName in e.defaultEventHandlers)
|
|
e.defaultEventHandlers[eventName](e, this);
|
|
}
|
|
|
|
/// this dispatches the element using the capture -> target -> bubble process
|
|
void dispatch() {
|
|
if(srcElement is null)
|
|
return;
|
|
|
|
// first capture, then bubble
|
|
|
|
Widget[] chain;
|
|
Widget curr = srcElement;
|
|
while(curr) {
|
|
auto l = curr;
|
|
chain ~= l;
|
|
curr = curr.parent;
|
|
}
|
|
|
|
isBubbling = false;
|
|
|
|
foreach_reverse(e; chain) {
|
|
if(eventName in e.capturingEventHandlers)
|
|
foreach(handler; e.capturingEventHandlers[eventName])
|
|
if(handler !is null)
|
|
handler(e, this);
|
|
|
|
// the default on capture should really be to always do nothing
|
|
|
|
//if(!defaultPrevented)
|
|
// if(eventName in e.defaultEventHandlers)
|
|
// e.defaultEventHandlers[eventName](e.element, this);
|
|
|
|
if(propagationStopped)
|
|
break;
|
|
}
|
|
|
|
isBubbling = true;
|
|
if(!propagationStopped)
|
|
foreach(e; chain) {
|
|
if(eventName in e.bubblingEventHandlers)
|
|
foreach(handler; e.bubblingEventHandlers[eventName])
|
|
if(handler !is null)
|
|
handler(e, this);
|
|
|
|
if(propagationStopped)
|
|
break;
|
|
}
|
|
|
|
if(!defaultPrevented)
|
|
foreach(e; chain) {
|
|
if(eventName in e.defaultEventHandlers)
|
|
e.defaultEventHandlers[eventName](e, this);
|
|
}
|
|
}
|
|
}
|
|
|
|
bool isAParentOf(Widget a, Widget b) {
|
|
if(a is null || b is null)
|
|
return false;
|
|
|
|
while(b !is null) {
|
|
if(a is b)
|
|
return true;
|
|
b = b.parent;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
struct WidgetAtPointResponse {
|
|
Widget widget;
|
|
int x;
|
|
int y;
|
|
}
|
|
|
|
WidgetAtPointResponse widgetAtPoint(Widget starting, int x, int y) {
|
|
assert(starting !is null);
|
|
auto child = starting.getChildAtPosition(x, y);
|
|
while(child) {
|
|
starting = child;
|
|
x -= child.x;
|
|
y -= child.y;
|
|
child = starting.widgetAtPoint(x, y).widget;//starting.getChildAtPosition(x, y);
|
|
if(child is starting)
|
|
break;
|
|
}
|
|
return WidgetAtPointResponse(starting, x, y);
|
|
}
|
|
|
|
version(win32_theming) {
|
|
import std.c.windows.windows;
|
|
|
|
alias HANDLE HTHEME;
|
|
|
|
// Since dmd doesn't offer uxtheme.lib, I'll load the dll at runtime instead
|
|
HMODULE uxtheme;
|
|
static this() {
|
|
uxtheme = LoadLibraryA("uxtheme.dll");
|
|
if(uxtheme) {
|
|
DrawThemeBackground = cast(typeof(DrawThemeBackground)) GetProcAddress(uxtheme, "DrawThemeBackground");
|
|
OpenThemeData = cast(typeof(OpenThemeData)) GetProcAddress(uxtheme, "OpenThemeData");
|
|
CloseThemeData = cast(typeof(CloseThemeData)) GetProcAddress(uxtheme, "CloseThemeData");
|
|
GetThemeSysColorBrush = cast(typeof(GetThemeSysColorBrush)) GetProcAddress(uxtheme, "CloseThemeData");
|
|
}
|
|
}
|
|
|
|
// everything from here is just win32 headers copy pasta
|
|
private:
|
|
extern(Windows):
|
|
|
|
HRESULT function(HTHEME, HDC, int, int, in RECT*, in RECT*) DrawThemeBackground;
|
|
HTHEME function(HWND, LPCWSTR) OpenThemeData;
|
|
HRESULT function(HTHEME) CloseThemeData;
|
|
HBRUSH function(HTHEME, int) GetThemeSysColorBrush;
|
|
|
|
HMODULE LoadLibraryA(LPCSTR);
|
|
BOOL FreeLibrary(HMODULE);
|
|
FARPROC GetProcAddress(HMODULE, LPCSTR);
|
|
// pragma(lib, "uxtheme");
|
|
|
|
BOOL GetClassInfoA(HINSTANCE, LPCSTR, WNDCLASS*);
|
|
}
|
|
|
|
version(win32_widgets) {
|
|
import core.sys.windows.windows;
|
|
import gdi = core.sys.windows.wingdi;
|
|
// import win32.commctrl;
|
|
// import win32.winuser;
|
|
|
|
pragma(lib, "comctl32");
|
|
|
|
shared static this() {
|
|
// http://msdn.microsoft.com/en-us/library/windows/desktop/bb775507(v=vs.85).aspx
|
|
INITCOMMONCONTROLSEX ic;
|
|
ic.dwSize = cast(DWORD) ic.sizeof;
|
|
ic.dwICC = ICC_UPDOWN_CLASS | ICC_WIN95_CLASSES | ICC_BAR_CLASSES | ICC_PROGRESS_CLASS | ICC_COOL_CLASSES | ICC_STANDARD_CLASSES | ICC_USEREX_CLASSES;
|
|
if(!InitCommonControlsEx(&ic)) {
|
|
//import std.stdio; writeln("ICC failed");
|
|
}
|
|
}
|
|
|
|
|
|
// everything from here is just win32 headers copy pasta
|
|
private:
|
|
extern(Windows):
|
|
|
|
alias HANDLE HMENU;
|
|
HMENU CreateMenu();
|
|
bool SetMenu(HWND, HMENU);
|
|
HMENU CreatePopupMenu();
|
|
enum MF_POPUP = 0x10;
|
|
enum MF_STRING = 0;
|
|
|
|
|
|
BOOL InitCommonControlsEx(const INITCOMMONCONTROLSEX*);
|
|
struct INITCOMMONCONTROLSEX {
|
|
DWORD dwSize;
|
|
DWORD dwICC;
|
|
}
|
|
enum HINST_COMMCTRL = cast(HINSTANCE) (-1);
|
|
enum {
|
|
IDB_STD_SMALL_COLOR,
|
|
IDB_STD_LARGE_COLOR,
|
|
IDB_VIEW_SMALL_COLOR = 4,
|
|
IDB_VIEW_LARGE_COLOR = 5
|
|
}
|
|
enum {
|
|
STD_CUT,
|
|
STD_COPY,
|
|
STD_PASTE,
|
|
STD_UNDO,
|
|
STD_REDOW,
|
|
STD_DELETE,
|
|
STD_FILENEW,
|
|
STD_FILEOPEN,
|
|
STD_FILESAVE,
|
|
STD_PRINTPRE,
|
|
STD_PROPERTIES,
|
|
STD_HELP,
|
|
STD_FIND,
|
|
STD_REPLACE,
|
|
STD_PRINT // = 14
|
|
}
|
|
|
|
alias HANDLE HIMAGELIST;
|
|
HIMAGELIST ImageList_Create(int, int, UINT, int, int);
|
|
int ImageList_Add(HIMAGELIST, HBITMAP, HBITMAP);
|
|
BOOL ImageList_Destroy(HIMAGELIST);
|
|
|
|
uint MAKELONG(ushort a, ushort b) {
|
|
return cast(uint) ((b << 16) | a);
|
|
}
|
|
|
|
|
|
struct TBBUTTON {
|
|
int iBitmap;
|
|
int idCommand;
|
|
BYTE fsState;
|
|
BYTE fsStyle;
|
|
BYTE bReserved[2]; // FIXME: isn't that different on 64 bit?
|
|
DWORD dwData;
|
|
int iString;
|
|
}
|
|
|
|
enum {
|
|
TB_ADDBUTTONSA = WM_USER + 20,
|
|
TB_INSERTBUTTONA = WM_USER + 21,
|
|
TB_GETIDEALSIZE = WM_USER + 99,
|
|
}
|
|
|
|
struct SIZE {
|
|
LONG cx;
|
|
LONG cy;
|
|
}
|
|
|
|
|
|
enum {
|
|
TBSTATE_CHECKED = 1,
|
|
TBSTATE_PRESSED = 2,
|
|
TBSTATE_ENABLED = 4,
|
|
TBSTATE_HIDDEN = 8,
|
|
TBSTATE_INDETERMINATE = 16,
|
|
TBSTATE_WRAP = 32
|
|
}
|
|
|
|
|
|
|
|
enum {
|
|
ILC_COLOR = 0,
|
|
ILC_COLOR4 = 4,
|
|
ILC_COLOR8 = 8,
|
|
ILC_COLOR16 = 16,
|
|
ILC_COLOR24 = 24,
|
|
ILC_COLOR32 = 32,
|
|
ILC_COLORDDB = 254,
|
|
ILC_MASK = 1,
|
|
ILC_PALETTE = 2048
|
|
}
|
|
|
|
|
|
alias TBBUTTON* PTBBUTTON, LPTBBUTTON;
|
|
|
|
|
|
enum {
|
|
TB_ENABLEBUTTON = WM_USER + 1,
|
|
TB_CHECKBUTTON,
|
|
TB_PRESSBUTTON,
|
|
TB_HIDEBUTTON,
|
|
TB_INDETERMINATE, // = WM_USER + 5,
|
|
TB_ISBUTTONENABLED = WM_USER + 9,
|
|
TB_ISBUTTONCHECKED,
|
|
TB_ISBUTTONPRESSED,
|
|
TB_ISBUTTONHIDDEN,
|
|
TB_ISBUTTONINDETERMINATE, // = WM_USER + 13,
|
|
TB_SETSTATE = WM_USER + 17,
|
|
TB_GETSTATE = WM_USER + 18,
|
|
TB_ADDBITMAP = WM_USER + 19,
|
|
TB_DELETEBUTTON = WM_USER + 22,
|
|
TB_GETBUTTON,
|
|
TB_BUTTONCOUNT,
|
|
TB_COMMANDTOINDEX,
|
|
TB_SAVERESTOREA,
|
|
TB_CUSTOMIZE,
|
|
TB_ADDSTRINGA,
|
|
TB_GETITEMRECT,
|
|
TB_BUTTONSTRUCTSIZE,
|
|
TB_SETBUTTONSIZE,
|
|
TB_SETBITMAPSIZE,
|
|
TB_AUTOSIZE, // = WM_USER + 33,
|
|
TB_GETTOOLTIPS = WM_USER + 35,
|
|
TB_SETTOOLTIPS = WM_USER + 36,
|
|
TB_SETPARENT = WM_USER + 37,
|
|
TB_SETROWS = WM_USER + 39,
|
|
TB_GETROWS,
|
|
TB_GETBITMAPFLAGS,
|
|
TB_SETCMDID,
|
|
TB_CHANGEBITMAP,
|
|
TB_GETBITMAP,
|
|
TB_GETBUTTONTEXTA,
|
|
TB_REPLACEBITMAP, // = WM_USER + 46,
|
|
TB_GETBUTTONSIZE = WM_USER + 58,
|
|
TB_SETBUTTONWIDTH = WM_USER + 59,
|
|
TB_GETBUTTONTEXTW = WM_USER + 75,
|
|
TB_SAVERESTOREW = WM_USER + 76,
|
|
TB_ADDSTRINGW = WM_USER + 77,
|
|
}
|
|
|
|
extern(Windows)
|
|
BOOL EnumChildWindows(HWND, WNDENUMPROC, LPARAM);
|
|
|
|
alias extern(Windows) BOOL function (HWND, LPARAM) WNDENUMPROC;
|
|
|
|
|
|
enum {
|
|
TB_SETINDENT = WM_USER + 47,
|
|
TB_SETIMAGELIST,
|
|
TB_GETIMAGELIST,
|
|
TB_LOADIMAGES,
|
|
TB_GETRECT,
|
|
TB_SETHOTIMAGELIST,
|
|
TB_GETHOTIMAGELIST,
|
|
TB_SETDISABLEDIMAGELIST,
|
|
TB_GETDISABLEDIMAGELIST,
|
|
TB_SETSTYLE,
|
|
TB_GETSTYLE,
|
|
//TB_GETBUTTONSIZE,
|
|
//TB_SETBUTTONWIDTH,
|
|
TB_SETMAXTEXTROWS,
|
|
TB_GETTEXTROWS // = WM_USER + 61
|
|
}
|
|
|
|
enum {
|
|
CCM_FIRST = 0x2000,
|
|
CCM_LAST = CCM_FIRST + 0x200,
|
|
CCM_SETBKCOLOR = 8193,
|
|
CCM_SETCOLORSCHEME = 8194,
|
|
CCM_GETCOLORSCHEME = 8195,
|
|
CCM_GETDROPTARGET = 8196,
|
|
CCM_SETUNICODEFORMAT = 8197,
|
|
CCM_GETUNICODEFORMAT = 8198,
|
|
CCM_SETVERSION = 0x2007,
|
|
CCM_GETVERSION = 0x2008,
|
|
CCM_SETNOTIFYWINDOW = 0x2009
|
|
}
|
|
|
|
|
|
enum {
|
|
PBM_SETRANGE = WM_USER + 1,
|
|
PBM_SETPOS,
|
|
PBM_DELTAPOS,
|
|
PBM_SETSTEP,
|
|
PBM_STEPIT, // = WM_USER + 5
|
|
PBM_SETRANGE32 = 1030,
|
|
PBM_GETRANGE,
|
|
PBM_GETPOS,
|
|
PBM_SETBARCOLOR, // = 1033
|
|
PBM_SETBKCOLOR = CCM_SETBKCOLOR
|
|
}
|
|
|
|
enum {
|
|
PBS_SMOOTH = 1,
|
|
PBS_VERTICAL = 4
|
|
}
|
|
|
|
enum {
|
|
ICC_LISTVIEW_CLASSES = 1,
|
|
ICC_TREEVIEW_CLASSES = 2,
|
|
ICC_BAR_CLASSES = 4,
|
|
ICC_TAB_CLASSES = 8,
|
|
ICC_UPDOWN_CLASS = 16,
|
|
ICC_PROGRESS_CLASS = 32,
|
|
ICC_HOTKEY_CLASS = 64,
|
|
ICC_ANIMATE_CLASS = 128,
|
|
ICC_WIN95_CLASSES = 255,
|
|
ICC_DATE_CLASSES = 256,
|
|
ICC_USEREX_CLASSES = 512,
|
|
ICC_COOL_CLASSES = 1024,
|
|
ICC_STANDARD_CLASSES = 0x00004000,
|
|
}
|
|
|
|
enum WM_USER = 1024;
|
|
enum SB_SETTEXT = WM_USER + 1; // SET TEXT A. It is +11 for W
|
|
}
|
|
|
|
|
|
|
|
enum GenericIcons : ushort {
|
|
None,
|
|
// these happen to match the win32 std icons numerically if you just subtract one from the value
|
|
Cut,
|
|
Copy,
|
|
Paste,
|
|
Undo,
|
|
Redo,
|
|
Delete,
|
|
New,
|
|
Open,
|
|
Save,
|
|
PrintPreview,
|
|
Properties,
|
|
Help,
|
|
Find,
|
|
Replace,
|
|
Print,
|
|
}
|
|
|
|
/*
|
|
http://msdn.microsoft.com/en-us/library/windows/desktop/bb775947%28v=vs.85%29.aspx#check_boxes
|
|
http://msdn.microsoft.com/en-us/library/windows/desktop/ms633574%28v=vs.85%29.aspx
|
|
http://msdn.microsoft.com/en-us/library/windows/desktop/bb775943%28v=vs.85%29.aspx
|
|
http://msdn.microsoft.com/en-us/library/windows/desktop/bb775951%28v=vs.85%29.aspx
|
|
http://msdn.microsoft.com/en-us/library/windows/desktop/ms632680%28v=vs.85%29.aspx
|
|
http://msdn.microsoft.com/en-us/library/windows/desktop/ms644996%28v=vs.85%29.aspx#message_box
|
|
http://www.sbin.org/doc/Xlib/chapt_03.html
|
|
|
|
http://msdn.microsoft.com/en-us/library/windows/desktop/bb760433%28v=vs.85%29.aspx
|
|
http://msdn.microsoft.com/en-us/library/windows/desktop/bb760446%28v=vs.85%29.aspx
|
|
http://msdn.microsoft.com/en-us/library/windows/desktop/bb760443%28v=vs.85%29.aspx
|
|
http://msdn.microsoft.com/en-us/library/windows/desktop/bb760476%28v=vs.85%29.aspx
|
|
*/
|