This commit is contained in:
Adam D. Ruppe 2013-10-04 20:59:36 -04:00
parent 25906483bd
commit 0b8ddc2470
7 changed files with 676 additions and 137 deletions

39
bmp.d
View File

@ -1,16 +1,40 @@
import core.stdc.stdio;
module arsd.bmp;
import arsd.color;
MemoryImage readBmp(string filename) {
import core.stdc.stdio;
FILE* fp = fopen((filename ~ "\0").ptr, "rb".ptr);
if(fp is null)
throw new Exception("can't open save file");
scope(exit) fclose(fp);
uint read4() { uint what; fread(&what, 4, 1, fp); return what; }
ushort read2(){ ushort what; fread(&what, 2, 1, fp); return what; }
ubyte read1() { return cast(ubyte) fgetc(fp); }
void specialFread(void* tgt, size_t size) {
fread(tgt, size, 1, fp);
}
return readBmpIndirect(&specialFread);
}
MemoryImage readBmp(in ubyte[] data) {
const(ubyte)[] current = data;
void specialFread(void* tgt, size_t size) {
while(size) {
*cast(ubyte*)(tgt) = current[0];
current = current[1 .. $];
tgt++;
size--;
}
}
return readBmpIndirect(&specialFread);
}
MemoryImage readBmpIndirect(void delegate(void*, size_t) fread) {
uint read4() { uint what; fread(&what, 4); return what; }
ushort read2(){ ushort what; fread(&what, 2); return what; }
ubyte read1(){ ubyte what; fread(&what, 1); return what; }
void require1(ubyte t, size_t line = __LINE__) {
if(read1() != t)
@ -234,6 +258,7 @@ MemoryImage readBmp(string filename) {
}
void writeBmp(MemoryImage img, string filename) {
import core.stdc.stdio;
FILE* fp = fopen((filename ~ "\0").ptr, "wb".ptr);
if(fp is null)
throw new Exception("can't open save file");
@ -351,13 +376,15 @@ void writeBmp(MemoryImage img, string filename) {
}
}
/*
/+
void main() {
import simpledisplay;
//import std.file;
//auto img = readBmp(cast(ubyte[]) std.file.read("/home/me/test2.bmp"));
auto img = readBmp("/home/me/test2.bmp");
import std.stdio;
writeln((cast(Object)img).toString());
displayImage(Image.fromMemoryImage(img));
//img.writeBmp("/home/me/test2.bmp");
}
*/
+/

21
jsvar.d
View File

@ -116,7 +116,7 @@ void main() {
}
// the WrappedNativeObject is disgusting
// but works.
// but works. sort of.
/*
Foop foop2;
@ -169,6 +169,18 @@ void main() {
a;
}, globals));
/*
globals.minigui = json!q{};
import arsd.minigui;
globals.minigui.createWindow = {
var v;
auto mw = new MainWindow();
v._object = new OpaqueNativeObject!(MainWindow)(mw);
v.loop = { mw.loop(); };
return v;
};
*/
repl(globals);
writeln("BACK IN D!");
@ -1146,6 +1158,7 @@ struct var {
}
}
// this doesn't really work
class WrappedNativeObject(T, bool wrapData = true) : PrototypeObject {
T nativeObject;
@ -1246,9 +1259,9 @@ class OpaqueNativeObject(T) : PrototypeObject {
this.item = t;
}
override string toString() const {
return item.toString();
}
//override string toString() const {
//return item.toString();
//}
override OpaqueNativeObject!T copy() {
auto n = new OpaqueNativeObject!T(item);

574
minigui.d
View File

@ -1,22 +1,136 @@
// http://msdn.microsoft.com/en-us/library/windows/desktop/bb775498%28v=vs.85%29.aspx
/// FOR BEST RESULTS: be sure to link with the appropriate subsystem command
/// -L/SUBSYSTEM:WINDOWS:5.0
/// otherwise you'll get a console and other visual bugs.
module arsd.minigui;
/*
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.
import simpledisplay;
public import simpledisplay;
// 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) {}
else {
version(custom_widgets) {
enum bool UsingCustomWidgets = true;
} else {
version = win32_widgets;
enum bool UsingCustomWidgets = false;
}
// and native theming when needed
version = win32_theming;
//version = win32_theming;
} else {
enum bool UsingCustomWidgets = true;
}
/*
@ -31,11 +145,30 @@ version(Windows) {
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
*/
enum windowBackgroundColor = Color(190, 190, 190);
static if(UsingSimpledisplayX11)
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) {
@ -75,6 +208,7 @@ class Action {
basic clipboard
* radio box
splitter
toggle buttons (optionally mutually exclusive, like in Paint)
label, rich text display, multi line plain text (selectable)
* fieldset
@ -131,16 +265,46 @@ enum LinePreference {
}
*/
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() { 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 margin() { return 0; }
int padding() { return 0; }
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() {
@ -155,12 +319,24 @@ void recomputeChildLayout(string relevantMeasure)(Widget parent) {
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) - parent.padding() * 2;
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 - child.margin() * 2 - parent.padding() * 2; // block element style
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())
@ -169,20 +345,32 @@ void recomputeChildLayout(string relevantMeasure)(Widget parent) {
} else {
if(child.height < 0)
child.height = 0;
child.height = parent.height - child.margin() * 2 - parent.padding() * 2;
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(stretchinessSum) {
while(spaceRemaining > 0 && stretchinessSum) {
//import std.stdio; writeln("str ", stretchinessSum);
auto spacePerChild = spaceRemaining / stretchinessSum;
if(spacePerChild == 0)
if(spacePerChild <= 0)
break;
int previousSpaceRemaining = spaceRemaining;
stretchinessSum = 0;
foreach(child; parent.children) {
static if(calcingV)
@ -200,35 +388,42 @@ void recomputeChildLayout(string relevantMeasure)(Widget parent) {
mixin("child." ~ relevantMeasure) += spaceAdjustment;
spaceRemaining -= spaceAdjustment;
if(mixin("child." ~ relevantMeasure) > maximum) {
auto diff = maximum - mixin("child." ~ relevantMeasure);
auto diff = mixin("child." ~ relevantMeasure) - maximum;
mixin("child." ~ relevantMeasure) -= diff;
spaceRemaining += diff;
} else if(mixin("child." ~ relevantMeasure) < maximum) {
stretchinessSum += mixin("child." ~ relevantMeasure ~ "Stretchiness()");
}
spaceRemaining -= child.margin();
}
if(spaceRemaining == previousSpaceRemaining)
break; // apparently nothing more we can do
}
int currentPos = parent.padding();
lastMargin = 0;
int currentPos = mixin("parent.padding"~firstThingy~"()");
foreach(child; parent.children) {
currentPos += child.margin();
auto margin = mixin("child.margin" ~ secondThingy ~ "()");
int thisMargin = mymax(lastMargin, mixin("child.margin"~firstThingy~"()"));
currentPos += thisMargin;
static if(calcingV) {
child.x = parent.padding() + child.margin();
child.x = parent.paddingLeft() + child.marginLeft();
child.y = currentPos;
} else {
child.x = currentPos;
child.y = parent.padding() + child.margin();
child.y = parent.paddingTop() + child.marginTop();
}
currentPos += mixin("child." ~ relevantMeasure);
currentPos += child.margin();
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)
@ -244,7 +439,7 @@ mixin template StyleInfo(string windowType) {
+/
// OK so we need to make getting at the native window stuff possible in simpledisplay.d
// and here, it must be integratable with the layout, the event system, and not be painted over.
// 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 {
@ -276,6 +471,8 @@ version(win32_widgets) {
else
phwnd = p.parentWindow.win.impl.hwnd;
assert(phwnd !is null);
style |= WS_VISIBLE | WS_CHILD;
p.hwnd = CreateWindow(toStringzInternal(className), toStringzInternal(windowText), style,
CW_USEDEFAULT, CW_USEDEFAULT, 100, 100,
@ -309,6 +506,16 @@ 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;
@ -404,7 +611,7 @@ class Widget {
else {
assert(position < children.length);
children.length = children.length + 1;
for(int i = children.length - 1; i > position; i--)
for(int i = cast(int) children.length - 1; i > position; i--)
children[i] = children[i - 1];
children[position] = w;
}
@ -472,23 +679,65 @@ class VerticalLayout : Widget {
// intentionally blank - widget's default is vertical layout right now
}
class HorizontalLayout : Widget {
this(Widget parent = null) { super(parent); }
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) {
this(int width = 500, int height = 500, string title = null) {
super(null);
win = new SimpleWindow(width, height);
win = new SimpleWindow(width, height, title, OpenGlOptions.no, Resizablity.allowResizing);
this.width = win.width;
this.height = win.height;
this.parentWindow = this;
@ -511,6 +760,24 @@ class Window : Widget {
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.
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.drawRectangle(Point(0, 0), this.width, this.height);
@ -545,6 +812,11 @@ class Window : Widget {
override bool dispatchMouseEvent(MouseEvent ev) {
auto ele = widgetAtPoint(this, ev.x, ev.y);
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);
@ -558,6 +830,7 @@ class Window : Widget {
event = new Event("click", ele);
event.clientX = ev.x;
event.clientY = ev.y;
event.button = ev.button;
event.dispatch();
}
} else if(ev.type == 0) {
@ -573,6 +846,9 @@ class Window : Widget {
event = new Event("mouseenter", ele);
event.relatedTarget = mouseLastOver;
event.sendDirectly();
static if(UsingSimpledisplayX11)
XDefineCursor(XDisplayConnection.get(), ele.parentWindow.win.impl.window, ele.cursor);
}
}
@ -625,7 +901,7 @@ class MainWindow : Window {
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();
this.statusBar.parts[0].content = _this.statusTip; // ~ " " ~ event.target.toString();
};
version(win32_widgets)
@ -648,10 +924,9 @@ class MainWindow : Window {
event.dispatch();
*/
} else {
auto buttonHandle = cast(HWND) lParam;
if(auto widget = buttonHandle in Widget.nativeMapping) {
auto event = new Event("triggered", *widget);
event.dispatch();
auto handle = cast(HWND) lParam;
if(auto widgetp = handle in Widget.nativeMapping) {
(*widgetp).handleWmCommand(HIWORD(wParam), LOWORD(wParam));
}
}
break;
@ -718,7 +993,15 @@ class MainWindow : Window {
Each button ought to correspond to a menu item.
*/
class ToolBar : Widget {
override int maxHeight() { return 40; }
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;
@ -743,10 +1026,22 @@ class ToolBar : Widget {
// FIXME: I_IMAGENONE is if here is no icon
foreach(action; actions)
buttons ~= TBBUTTON(MAKELONG(action.iconId, 0), action.id, TBSTATE_ENABLED, 0, 0, 0, cast(int) toStringzInternal(action.label));
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));
@ -773,11 +1068,15 @@ class ToolButton : Button {
};
paint = (ScreenPainter painter) {
painter.outlineColor = Color.black;
painter.outlineColor = windowBackgroundColor;
if(isHovering) {
painter.fillColor = Color.transparent;
painter.drawRectangle(Point(0, 0), width, height);
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);
};
}
@ -786,7 +1085,6 @@ class ToolButton : Button {
Action action;
override int maxWidth() { return 40; }
override int minWidth() { return 40; }
}
@ -842,8 +1140,8 @@ class MenuBar : Widget {
.recomputeChildLayout!"width"(this);
}
override int maxHeight() { return Window.lineHeight; }
override int minHeight() { return Window.lineHeight; }
override int maxHeight() { return Window.lineHeight + 4; }
override int minHeight() { return Window.lineHeight + 4; }
}
@ -863,7 +1161,7 @@ class StatusBar : Widget {
@disable this();
this(StatusBar owner) { this.owner = owner; }
//@disable this(this);
@property int length() { return owner.partsArray.length; }
@property int length() { return cast(int) owner.partsArray.length; }
private StatusBar owner;
private this(StatusBar owner, Part[] parts) {
this.owner.partsArray = parts;
@ -878,7 +1176,7 @@ class StatusBar : Widget {
Part opOpAssign(string op : "~" )(Part p) {
assert(owner.partsArray.length < 255);
p.owner = this.owner;
p.idx = owner.partsArray.length;
p.idx = cast(int) owner.partsArray.length;
owner.partsArray ~= p;
version(win32_widgets) {
int[256] pos;
@ -937,6 +1235,11 @@ class StatusBar : Widget {
version(win32_widgets) {
parentWindow = parent.parentWindow;
createWin32Window(this, "msctls_statusbar32", "D rox", 0);
RECT rect;
GetWindowRect(hwnd, &rect);
idealHeight = rect.bottom - rect.top;
assert(idealHeight);
} else {
this.paint = (ScreenPainter painter) {
painter.outlineColor = Color.black;
@ -951,8 +1254,14 @@ class StatusBar : Widget {
}
}
override int maxHeight() { return Window.lineHeight; }
override int minHeight() { return Window.lineHeight; }
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
@ -1038,11 +1347,17 @@ class ProgressBar : Widget {
}
class Fieldset : Widget {
override int padding() { return 8; }
override int margin() { return 4; }
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);
@ -1051,23 +1366,43 @@ class Fieldset : Widget {
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(220, 220, 220);
painter.outlineColor = Color.black;
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 = padding * 2;
foreach(child; children)
auto m = paddingTop() + paddingBottom();
foreach(child; children) {
m += child.maxHeight();
return m;
m += child.marginBottom();
m += child.marginTop();
}
return m + 6;
}
override int minHeight() {
return super.minHeight() + Window.lineHeight + 4;
}
}
@ -1081,23 +1416,25 @@ class Menu : Widget {
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 = parent.width;
this.width = 150;
if(this.children.length)
this.height = this.children.length * this.children[0].maxHeight();
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 = Color(190, 190, 190);
painter.fillColor = lighten(windowBackgroundColor, 0.8);
painter.drawRectangle(Point(0, 0), width, height);
};
@ -1109,6 +1446,8 @@ class Menu : Widget {
ev.stopPropagation();
};
parentWindow.captureMouse(this);
foreach(child; children)
child.parentWindow = this.parentWindow;
@ -1157,10 +1496,14 @@ class MenuItem : MouseActivatedWidget {
Action action;
string label;
override int maxHeight() { return Window.lineHeight; }
override int minWidth() { return Window.lineHeight * label.length; }
override int maxWidth() { return Window.lineHeight / 2 * label.length; }
this(string lbl, Window parent = null) {
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
@ -1169,11 +1512,11 @@ class MenuItem : MouseActivatedWidget {
painter.outlineColor = Color.blue;
else
painter.outlineColor = Color.black;
painter.drawText(Point(0, 0), label, Point(width, height), TextAlignment.Center);
painter.drawText(Point(cast(MenuBar) this.parent ? 4 : 20, 2), label, Point(width, height), TextAlignment.Left);
};
}
this(Action action, Window parent = null) {
this(Action action, Widget parent = null) {
assert(action !is null);
this(action.label);
this.action = action;
@ -1182,6 +1525,9 @@ class MenuItem : MouseActivatedWidget {
//event.dispatch();
foreach(handler; action.triggered)
handler();
if(auto pmenu = cast(Menu) this.parent)
pmenu.remove();
};
}
}
@ -1249,6 +1595,7 @@ 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) {
@ -1288,6 +1635,14 @@ class Checkbox : MouseActivatedWidget {
}
}
class VerticalSpacer : Widget {
override int maxHeight() { return 20; }
override int minHeight() { return 20; }
this(Widget parent = null) {
super(parent);
}
}
class MutuallyExclusiveGroup {
MouseActivatedWidget[] members;
@ -1322,7 +1677,7 @@ class Radiobox : MouseActivatedWidget {
this(string label, Widget parent = null) {
super(parent);
height = 16;
width = height + 4 + label.length * 16;
width = height + 4 + cast(int) label.length * 16;
this.paint = (ScreenPainter painter) {
painter.outlineColor = Color.black;
@ -1359,6 +1714,12 @@ class Button : MouseActivatedWidget {
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) {
@ -1408,6 +1769,8 @@ class Button : MouseActivatedWidget {
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 {
@ -1435,8 +1798,18 @@ int[2] getChildPositionRelativeToParentHwnd(Widget c) nothrow {
return [x, y];
}
class LineEdit : Widget {
version(win32_widgets)
this(Widget parent = null) {
super(parent);
parentWindow = parent.parentWindow;
createWin32Window(this, "edit", "",
WS_BORDER|WS_HSCROLL|ES_AUTOHSCROLL);
}
}
class TextEdit : Widget {
override int minHeight() { return Window.lineHeight; }
override int heightStretchiness() { return 3; }
override int widthStretchiness() { return 3; }
@ -1468,6 +1841,8 @@ class TextEdit : Widget {
redraw();
};
static if(UsingSimpledisplayX11)
cursor = XCreateFontCursor(XDisplayConnection.get(), 152 /* XC_xterm, a text input thingy */);
//super();
}
@ -1493,10 +1868,10 @@ class MessageBox : Window {
this(string message) {
super(300, 100);
auto superPaint = this.paint;
this.paint = (ScreenPainter painter) {
painter.fillColor = Color(192, 192, 192);
painter.drawRectangle(Point(0, 0), this.width, this.height);
if(superPaint)
superPaint(painter);
painter.outlineColor = Color.black;
painter.drawText(Point(0, 0), message, Point(width, height / 2), TextAlignment.Center | TextAlignment.VerticalCenter);
};
@ -1725,6 +2100,39 @@ Widget widgetAtPoint(Widget starting, int x, int y) {
return starting;
}
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 std.c.windows.windows;
@ -1737,7 +2145,7 @@ version(win32_widgets) {
// 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_WIN95_CLASSES | ICC_BAR_CLASSES | ICC_PROGRESS_CLASS | ICC_COOL_CLASSES;
ic.dwICC = ICC_UPDOWN_CLASS | ICC_WIN95_CLASSES | ICC_BAR_CLASSES | ICC_PROGRESS_CLASS | ICC_COOL_CLASSES | ICC_STANDARD_CLASSES;
InitCommonControlsEx(&ic);
}
@ -1808,9 +2216,15 @@ struct TBBUTTON {
enum {
TB_ADDBUTTONSA = WM_USER + 20,
TB_INSERTBUTTONA = WM_USER + 21
TB_INSERTBUTTONA = WM_USER + 21,
TB_GETIDEALSIZE = WM_USER + 99,
}
struct SIZE {
LONG cx;
LONG cy;
}
enum {
TBSTATE_CHECKED = 1,
@ -1946,7 +2360,8 @@ enum {
ICC_WIN95_CLASSES = 255,
ICC_DATE_CLASSES = 256,
ICC_USEREX_CLASSES = 512,
ICC_COOL_CLASSES = 1024
ICC_COOL_CLASSES = 1024,
ICC_STANDARD_CLASSES = 0x00004000,
}
enum WM_USER = 1024;
@ -1955,15 +2370,24 @@ enum {
version(win32_widgets)
enum GenericIcons : ushort {
New = STD_FILENEW,
Open = STD_FILEOPEN,
Save = STD_FILESAVE,
}
else
enum GenericIcons : ushort {
New, Open, Save
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,
}
/*

14
oauth.d
View File

@ -234,6 +234,20 @@ OAuthParams twitter(string apiKey, string apiSecret) {
return params;
}
OAuthParams tumblr(string apiKey, string apiSecret) {
OAuthParams params;
params.apiKey = apiKey;
params.apiSecret = apiSecret;
params.baseUrl = "http://www.tumblr.com";
params.requestTokenPath = "/oauth/request_token";
params.authorizePath = "/oauth/authorize";
params.accessTokenPath = "/oauth/access_token";
return params;
}
OAuthParams linkedIn(string apiKey, string apiSecret) {
OAuthParams params;

12
png.d
View File

@ -26,7 +26,15 @@ import simpledisplay;
import std.file;
void main(string[] args) {
auto img = imageFromPng(readPng(cast(ubyte[]) read(args[1])));
// older api, the individual functions give you more control if you need it
//auto img = imageFromPng(readPng(cast(ubyte[]) read(args[1])));
// newer api, simpler but less control
auto img = readPng(args[1]);
// displayImage is from simpledisplay and just pops up a window to show the image
// simpledisplay's Images are a little different than MemoryImages that this loads,
// but conversion is easy
displayImage(Image.fromMemoryImage(img));
}
*/
@ -452,7 +460,7 @@ PngHeader getHeaderFromFile(string filename) {
return getHeader(png);
}
PNG* readPng(ubyte[] data) {
PNG* readPng(in ubyte[] data) {
auto p = new PNG;
p.length = data.length;

View File

@ -1,5 +1,8 @@
module simpledisplay;
// FIXME: SIGINT handler is necessary to clean up shared memory handles upon ctrl+c
// Note: if you are using Image on X, you might want to do:
/*
static if(UsingSimpledisplayX11) {
@ -10,7 +13,7 @@ module simpledisplay;
}
If the shared memory extension is available though, simpledisplay uses it
for a significant speed boost whenever you draw Images.
for a significant speed boost whenever you draw large Images.
*/
// CHANGE FROM LAST VERSION: the window background is no longer fixed, so you might want to fill the screen with a particular color before drawing.
@ -214,17 +217,17 @@ enum RasterOp {
// being phobos-free keeps the size WAY down
private const(char)* toStringz(string s) { return (s ~ '\0').ptr; }
private string[] split(string a, char c) {
private string[] split(in void[] a, char c) {
string[] ret;
size_t previous = 0;
foreach(i, char ch; a) {
foreach(i, char ch; cast(ubyte[]) a) {
if(ch == c) {
ret ~= a[previous .. i];
ret ~= cast(string) a[previous .. i];
previous = i + 1;
}
}
if(previous != a.length)
ret ~= a[previous .. $];
ret ~= cast(string) a[previous .. $];
return ret;
}
@ -292,6 +295,35 @@ enum TextAlignment : uint {
public import arsd.color; // no longer stand alone... :-( but i need a common type for this to work with images easily.
version(X11)
enum ModifierState : uint {
shift = 1,
capsLock = 2,
ctrl = 4,
alt = 8,
numLock = 16,
windows = 64,
// these aren't available on Windows for key events, so don't use them for that unless your app is X only.
leftButtonDown = 256,
middleButtonDown = 512,
rightButtonDown = 1024,
}
else version(Windows)
enum ModifierState : uint {
shift = 4,
ctrl = 8,
alt = 256,
windows = 512,
capsLock = 1024,
numLock = 2048,
leftButtonDown = 1,
middleButtonDown = 16,
rightButtonDown = 2,
}
struct KeyEvent {
/// see table below. Always use the symbolic names, even for ASCII characters, since the actual numbers vary across platforms.
Key key;
@ -300,11 +332,7 @@ struct KeyEvent {
dchar character;
// state:
// 1 == shift
// 8 == alt
// 4 == ctrl
uint modifierState;
uint modifierState; /// see enum ModifierState
SimpleWindow window;
}
@ -698,7 +726,7 @@ version(X11) {
// FIXME: mouse move should be distinct from presses+releases, so we can avoid subscribing to those events in X unnecessarily
/// Listen for this on your event listeners if you are interested in mouse
struct MouseEvent {
int type; // movement, press, release, double click
MouseEventType type; // movement, press, release, double click
int x;
int y;
@ -706,8 +734,8 @@ struct MouseEvent {
int dx;
int dy;
int button;
int buttonFlags;
MouseButton button;
int modifierState;
SimpleWindow window;
}
@ -1153,7 +1181,7 @@ class Sprite {
version(X11) {
auto display = XDisplayConnection.get();
handle = XCreatePixmap(display, cast(Drawable) win.window, width, height, 24);
if(i.xshmAvailable)
if(i.usingXshm)
XShmPutImage(display, cast(Drawable) handle, DefaultGC(display, DefaultScreen(display)), i.handle, 0, 0, 0, 0, i.width, i.height, false);
else
XPutImage(display, cast(Drawable) handle, DefaultGC(display, DefaultScreen(display)), i.handle, 0, 0, 0, 0, i.width, i.height);
@ -1391,7 +1419,7 @@ class SimpleWindow {
} else
version(X11) {
if(!destroyed) {
if(i.xshmAvailable)
if(i.usingXshm)
XShmPutImage(display, cast(Drawable) window, gc, i.handle, 0, 0, 0, 0, i.width, i.height, false);
else
XPutImage(display, cast(Drawable) window, gc, i.handle, 0, 0, 0, 0, i.width, i.height);
@ -1845,7 +1873,7 @@ version(Windows) {
mouse.x = LOWORD(lParam) + offsetX;
mouse.y = HIWORD(lParam) + offsetY;
wind.mdx(mouse);
mouse.buttonFlags = wParam;
mouse.modifierState = wParam;
mouse.window = wind;
if(wind.handleMouseEvent)
@ -1866,7 +1894,19 @@ version(Windows) {
ev.pressed = msg == WM_KEYDOWN;
// FIXME
// ev.hardwareCode
// ev.modifierState =
if(GetKeyState(Key.Shift)&0x8000 || GetKeyState(Key.Shift_r)&0x8000)
ev.modifierState |= ModifierState.shift;
if(GetKeyState(Key.Alt)&0x8000 || GetKeyState(Key.Alt_r)&0x8000)
ev.modifierState |= ModifierState.alt;
if(GetKeyState(Key.Ctrl)&0x8000 || GetKeyState(Key.Ctrl_r)&0x8000)
ev.modifierState |= ModifierState.ctrl;
if(GetKeyState(Key.Windows)&0x8000 || GetKeyState(Key.Windows_r)&0x8000)
ev.modifierState |= ModifierState.windows;
if(GetKeyState(Key.NumLock))
ev.modifierState |= ModifierState.numLock;
if(GetKeyState(Key.CapsLock))
ev.modifierState |= ModifierState.capsLock;
/+
// we always want to send the character too, so let's convert it
@ -1886,45 +1926,45 @@ version(Windows) {
wind.handleKeyEvent(ev);
break;
case 0x020a /*WM_MOUSEWHEEL*/:
mouse.type = 1;
mouse.button = (HIWORD(wParam) > 120) ? 16 : 8;
mouse.type = cast(MouseEventType) 1;
mouse.button = cast(MouseButton) ((HIWORD(wParam) > 120) ? 16 : 8);
mouseEvent();
break;
case WM_MOUSEMOVE:
mouse.type = 0;
mouse.type = cast(MouseEventType) 0;
mouseEvent();
break;
case WM_LBUTTONDOWN:
case WM_LBUTTONDBLCLK:
mouse.type = 1;
mouse.button = 1;
mouse.type = cast(MouseEventType) 1;
mouse.button = cast(MouseButton) 1;
mouseEvent();
break;
case WM_LBUTTONUP:
mouse.type = 2;
mouse.button = 1;
mouse.type = cast(MouseEventType) 2;
mouse.button =cast(MouseButton) 1;
mouseEvent();
break;
case WM_RBUTTONDOWN:
case WM_RBUTTONDBLCLK:
mouse.type = 1;
mouse.button = 2;
mouse.type = cast(MouseEventType) 1;
mouse.button =cast(MouseButton) 2;
mouseEvent();
break;
case WM_RBUTTONUP:
mouse.type = 2;
mouse.button = 2;
mouse.type = cast(MouseEventType) 2;
mouse.button =cast(MouseButton) 2;
mouseEvent();
break;
case WM_MBUTTONDOWN:
case WM_MBUTTONDBLCLK:
mouse.type = 1;
mouse.button = 4;
mouse.type = cast(MouseEventType) 1;
mouse.button = cast(MouseButton) 4;
mouseEvent();
break;
case WM_MBUTTONUP:
mouse.type = 2;
mouse.button = 4;
mouse.type = cast(MouseEventType) 2;
mouse.button = cast(MouseButton) 4;
mouseEvent();
break;
default: return 1;
@ -2310,7 +2350,7 @@ version(X11) {
void drawImage(int x, int y, Image i, int ix, int iy, int w, int h) {
// source x, source y
if(i.xshmAvailable)
if(i.usingXshm)
XShmPutImage(display, d, gc, i.handle, ix, iy, x, y, w, h, false);
else
XPutImage(display, d, gc, i.handle, ix, iy, x, y, w, h);
@ -2346,10 +2386,12 @@ version(X11) {
void drawText(in int x, in int y, in int x2, in int y2, in string originalText, in uint alignment) {
// FIXME: we should actually draw unicode.. but until then, I'm going to strip out multibyte chars
string text;
immutable(ubyte)[] text;
// the first 256 unicode codepoints are the same as ascii and latin-1, which is what X expects, so we can keep all those
// then strip the rest so there isn't garbage
foreach(dchar ch; originalText)
if(ch < 128)
text ~= ch;
if(ch < 256)
text ~= cast(ubyte) ch;
if(text.length == 0)
return;
@ -2526,12 +2568,17 @@ version(X11) {
return _xshmAvailable;
}
bool usingXshm;
void createImage(int width, int height) {
auto display = XDisplayConnection.get();
assert(display !is null);
auto screen = DefaultScreen(display);
if(xshmAvailable) {
// it will only use shared memory for somewhat largish images,
// since otherwise we risk wasting shared memory handles on a lot of little ones
if(xshmAvailable && width > 100 && height > 100) {
usingXshm = true;
handle = XShmCreateImage(
display,
DefaultVisual(display, screen),
@ -2544,7 +2591,8 @@ version(X11) {
assert(handle.bytes_per_line == 4 * width);
shminfo.shmid = shmget(IPC_PRIVATE, handle.bytes_per_line * height, IPC_CREAT | 511 /* 0777 */);
assert(shminfo.shmid >= 0);
//import std.conv; import core.stdc.errno;
assert(shminfo.shmid >= 0);//, to!string(errno));
handle.data = shminfo.shmaddr = rawData = cast(ubyte*) shmat(shminfo.shmid, null, 0);
assert(rawData != cast(ubyte*) -1);
shminfo.readOnly = 0;
@ -2570,11 +2618,12 @@ version(X11) {
void dispose() {
// note: this calls free(rawData) for us
if(handle) {
if(xshmAvailable)
if(usingXshm)
XShmDetach(XDisplayConnection.get(), &shminfo);
XDestroyImage(handle);
if(xshmAvailable)
if(usingXshm) {
shmdt(shminfo.shmaddr);
}
handle = null;
}
}
@ -2900,10 +2949,10 @@ version(X11) {
MouseEvent mouse;
auto event = e.xmotion;
mouse.type = 0;
mouse.type = MouseEventType.motion;
mouse.x = event.x;
mouse.y = event.y;
mouse.buttonFlags = event.state;
mouse.modifierState = event.state;
if(auto win = e.xmotion.window in SimpleWindow.nativeMapping) {
(*win).mdx(mouse);
@ -2920,23 +2969,23 @@ version(X11) {
MouseEvent mouse;
auto event = e.xbutton;
mouse.type = e.type == EventType.ButtonPress ? 1 : 2;
mouse.type = cast(MouseEventType) (e.type == EventType.ButtonPress ? 1 : 2);
mouse.x = event.x;
mouse.y = event.y;
switch(event.button) {
case 1: mouse.button = 1; break; // left
case 2: mouse.button = 4; break; // middle
case 3: mouse.button = 2; break; // right
case 4: mouse.button = 8; break; // scroll up
case 5: mouse.button = 16; break; // scroll down
case 1: mouse.button = MouseButton.left; break; // left
case 2: mouse.button = MouseButton.middle; break; // middle
case 3: mouse.button = MouseButton.right; break; // right
case 4: mouse.button = MouseButton.wheelUp; break; // scroll up
case 5: mouse.button = MouseButton.wheelDown; break; // scroll down
default:
}
// FIXME: double check this
mouse.buttonFlags = event.state;
mouse.modifierState = event.state;
//mouse.buttonFlags = event.detail;
//mouse.modifierState = event.detail;
if(auto win = e.xbutton.window in SimpleWindow.nativeMapping) {
(*win).mdx(mouse);
@ -3197,6 +3246,8 @@ nothrow:
HBRUSH GetSysColorBrush(int nIndex);
DWORD GetSysColor(int nIndex);
SHORT GetKeyState(int nVirtKey);
int SetROP2(HDC, int);
enum R2_XORPEN = 7;
enum R2_COPYPEN = 13;

View File

@ -593,6 +593,8 @@ http://msdn.microsoft.com/en-us/library/windows/desktop/ms683193%28v=vs.85%29.as
size.X = cast(short) GetSystemMetrics(SM_CXMIN);
size.Y = cast(short) GetSystemMetrics(SM_CYMIN);
*/
// FIXME: this sucks, maybe i should just revert it. but there shouldn't be scrollbars in cellular mode
size.X = 80;
size.Y = 24;
SetConsoleScreenBufferSize(hConsole, size);