This is your entry point to create your own visual theme for custom widgets.
A line edit box with an associated label.
A labeled password edit.
Convenience import to override the Windows GDI Rectangle function (you can still use it through fully-qualified imports)
Methods marked with this are available from scripts if added to the arsd.script engine.
An Action represents some kind of user action they can trigger through menu options, toolbars, hotkeys, and similar mechanisms. The text label, icon, and handlers are centrally held here instead of repeated in each UI element.
Interface to a custom visual theme which is able to access and use style hint properties, draw stylistic elements, and even completely override existing class' paint methods (though I'd note that can be a lot harder than it may seem due to the various little details of state you need to reflect visually, so that should be your last result!)
Creates a push button with unbounded size. When it is clicked, it emits a triggered event.
Single-value widgets (that is, ones with a programming interface that just expose a value that the user has control over) should emit this after their value changes.
You should generally use a ChangeEvent!Type instead of this directly. See ChangeEvent for more information.
Indicates that a character has been typed by the user. Normally dispatched to the currently focused widget.
A basic checked or not checked box with an attached label.
Indicates that the user has worked with the mouse over your widget. For available properties, see MouseEventBase.
ClosingEvent is fired when a user is attempting to close a window. You can preventDefault to cancel the close.
ClosingEvent is fired when a user is attempting to close a window. You can preventDefault to cancel the close.
A collapsable sidebar is a container that shows if its assigned width is greater than its minimum and otherwise shows as a button.
A combination of free entry with a list below it.
A button with a consistent size, suitable for user commands like OK and CANCEL.
Command Events are used with a widget wants to issue a higher-level, yet loosely coupled command do its parents and other interested listeners, for example, "scroll up".
A CommandEvent is typically actually an instance of these to hold the strongly-typed arguments.
A widget specifically designed to hold other widgets.
A LineEdit is an editor of a single line of text, comparable to a HTML <input type="text" />.
A LineEdit that displays * in place of the actual characters.
A TextEdit is a multi-line plain text editor, comparable to a HTML <textarea>.
The data controller widget is created by reflecting over the given data type. You can use ControlledBy as a UDA on a struct or just let it create things automatically.
A custom widget similar to the HTML5 <details> tag.
A dialog is a transient window that intends to get information from the user before being dismissed.
Indicates that the user has worked with the mouse over your widget. For available properties, see MouseEventBase.
A drop-down list where the user must select one of the given options. Like <select> in HTML.
Contains the implementation of text editing and shared basic api. You should construct one of the child classes instead, like TextEdit, LineEdit, or PasswordEdit.
Represents an event that is currently being processed.
Creates the fieldset (also known as a group box) with the given label. A fieldset is generally used a container for mutually exclusive Radioboxs.
It is possible to override or customize the file dialog in some cases. These members provide those hooks: you do fileDialogDelegate = new YourSubclassOf_FileDialogDelegate; and you can do your own thing.
FixedPosition is like StaticPosition, but its coordinates are always relative to the viewport, meaning they do not scroll with the parent content.
FocusInEvent is a FocusEvent that propagates, while FocusOutEvent is a BlurEvent that propagates.
A text box with a drop down arrow listing selections. The user can choose from the list, or type their own.
A scrollable viewer for an array of widgets. The widgets inside a list item can be whatever you want, and you can have any number of total items you want because only the visible widgets need to actually exist and load their data at a time, giving constantly predictable performance.
A scrollable viewer for an array of widgets. The widgets inside a list item can be whatever you want, and you can have any number of total items you want because only the visible widgets need to actually exist and load their data at a time, giving constantly predictable performance.
This is emitted by the TableView when a user clicks on a column header.
Stacks the widgets horizontally, taking all the available height for each child.
Draws a line
Adds empty space to a layout.
Displays an in-progress indicator without known values
Makes all children minimum width and height, placing them down left to right, top to bottom.
Indicates that the user has pressed a key on the keyboard, or if they've been holding it long enough to repeat (key down events are sent both on the initial press then repeated by the OS on its own time.) For available properties, see KeyEventBase.
Contains shared properties for KeyDownEvents and KeyUpEvents.
Indicates that the user has released a key on the keyboard. For available properties, see KeyEventBase.
A LineEdit is an editor of a single line of text, comparable to a HTML <input type="text" />.
A list widget contains a list of strings that the user can examine and select.
A MainWindow is a window that includes turnkey support for a menu bar, tool bar, and status bar automatically positioned around a client area where you put your widgets.
You can make one of thse yourself but it is generally easer to use MainWindow.setMenuAndToolbarFromAnnotatedCode.
A MenuItem belongs to a Menu - use Menu.addItem to add one - and calls an Action when it is clicked.
A "mouse activiated widget" is really just an abstract variant of button.
Indicates that the user has worked with the mouse over your widget. For available properties, see MouseEventBase.
Contains shared properties for various mouse events;
Indicates that the user has worked with the mouse over your widget. For available properties, see MouseEventBase.
A mouse tracking widget is one that follows the mouse when dragged inside it.
Indicates that the user has worked with the mouse over your widget. For available properties, see MouseEventBase.
A helper to make widgets out of other native windows.
Nests an opengl capable window inside this window as a widget.
A page widget is basically a tab widget with hidden tabs. It is also sometimes called a "StackWidget".
A LineEdit that displays * in place of the actual characters.
A progress bar with a known endpoint and completion amount
Creates a radio button with an associated label. These are usually put inside a Fieldset.
A widget that takes your widget, puts scroll bars around it, and sends messages to it when the user scrolls. Unlike ScrollableWidget, it makes no effort to automatically scroll or clip its child widgets - it just sends the messages.
A widget meant to contain other widgets that may need to scroll.
A widget that tries (with, at best, limited success) to offer scrolling that is transparent to the inner.
A slider, also known as a trackbar control, is commonly used in applications like volume controls where you want the user to select a value between a min and a max without needing a specific value or otherwise precise input.
Event fired when an Observeable variable changes. You will want to add an event listener referencing the field like widget.addEventListener((scope StateChanged!(Whatever.field) ev) { });
Bypasses automatic layout for its children, using manual positioning and sizing only. While you need to manually position them, you must ensure they are inside the StaticLayout's bounding box to avoid undefined behavior.
Bypasses automatic positioning when being laid out. It is your responsibility to make room for this widget in the parent layout.
Status bars appear at the bottom of a MainWindow. They are made out of Parts, with a width and content.
A tab widget is a set of clickable tab buttons followed by a content area.
A TableView is a widget made to display a table of data strings.
A read-only text display. It is based on the editable widget base, but does not allow user edits and displays it on the direct background instead of on an editable background.
A TextEdit is a multi-line plain text editor, comparable to a HTML <textarea>.
Toolbars are lists of buttons (typically icons) that appear under the menu. Each button ought to correspond to a menu item, represented by Action objects.
Stacks the widgets vertically, taking all the available width for each child.
Draws a line
Adds empty space to a layout.
This is your entry point to create your own visual theme for custom widgets.
The Widget is the base class for minigui's functionality, ranging from UI components like checkboxes or text displays to abstract groupings of other widgets like a layout container or a html <div>. You will likely want to use pre-made widgets as well as creating your own.
EXPERIMENTAL
The purpose of this enum was to give a compile-time checked version of various standard event strings.
Identifies the button the user pressed on a message box.
For ScrollableWidget, determines when to show the scroll bar to the user. NEVER USED
User-defined attribute you can add to struct members contrlled by addDataControllerWidget or dialog to tell which widget you want created for them.
Intended for UFCS action like window.addDataControllerWidget(new MyObject());
Convenience function to add a triggered event listener.
Declares that the given widget consumes a command identified by the CommandString AND containing Args. Your handler is called with the arguments, then the event's propagation is stopped, so it will not be seen by the consumer's parents.
Context menus can have @hotkey, @label, @tip, @separator, and @icon
Context menus can have @hotkey, @label, @tip, @separator, and @icon
Calls MS Windows' CreateWindowExW function to create a native backing for the given widget. It will create needed mappings, window procedure hooks, and other private member variables needed to tie it into the rest of minigui's expectations.
Creates a dialog based on a data structure.
Emits a command to the sender widget's parents with the given CommandString and args. You have no way of knowing if it was ever actually consumed due to the loose coupling. Instead, the consumer may broadcast a state update back toward you.
It is possible to override or customize the file dialog in some cases. These members provide those hooks: you do fileDialogDelegate = new YourSubclassOf_FileDialogDelegate; and you can do your own thing.
Gets a file name for an open or save operation, calling your onOK function when the user has selected one. This function may or may not block depending on the operating system, you MUST assume it will complete asynchronously.
Displays a modal message box, blocking until the user dismisses it. These global ones are discouraged in favor of the same methods on Window, which give better user experience since the message box is tied the parent window instead of acting independently.
Observes and allows inspection of an object via automatic gui
Group: generating_from_code
This is your entry point to create your own visual theme for custom widgets.
This lets you statically verify you send the events you claim you send and gives you a hook to document them.
Convenience mixin for overriding all four sides of margin or padding in a Widget with the same code. It mixes in the given string as the return value of the four overridden methods.
Observable varables can be added to widgets and when they are changed, it fires off a StateChanged event so you can react to it.
Convenience mixin for overriding all four sides of margin or padding in a Widget with the same code. It mixes in the given string as the return value of the four overridden methods.
Implementation detail of the ControlledBy UDA.
This is an opaque type you can use to disconnect an event handler when you're no longer interested.
Used in automatic menu functions to indicate that the user should be able to browse for a file.
Get this through Widget.getComputedStyle. It provides access to the Widget.Style style hints and Widget layout hints, possibly modified through the VisualTheme, through a unifed interface.
Structure to represent a collection of background hints. New features can be added here, so make sure you use the provided constructors and factories for maximum compatibility.
Encapsulates the simpledisplay ScreenPainter for use on a Widget, with VisualTheme and invalidated area awareness.
Program-wide keyboard shortcut to trigger the action
Group: generating_from_code
Group: generating_from_code
Group: generating_from_code
tells which menu the action will be on
This item in the menu will be preceded by a separator line
Group: generating_from_code
Describes which toolbar section the action appears on
This is a helper for addDataControllerWidget. You can use it as a UDA on the type. See http://dpldocs.info/this-week-in-d/Blog.Posted_2020_11_02.html for more information.
The default string FileName refers to to store the last file referenced. You can use this if you like, or provide a different variable to FileName in your function.
A gui application is made out of widgets laid out in windows that display information and respond to events from the user. They also typically have actions available in menus, and you might also want to customize the appearance. How do we do these things with minigui? Let's break it down into several categories.
You will typically want to create the ui, prepare event handlers, then run an event loop. The event loop drives the program, calling your methods to respond to user activity.
import arsd.minigui; void main() { // first, create a window, the (optional) string here is its title auto window = new MainWindow("Hello, World!"); // lay out some widgets inside the window to create the ui auto name = new LabeledLineEdit("What is your name?", window); auto button = new Button("Say Hello", window); // prepare event handlers button.addEventListener(EventType.triggered, () { window.messageBox("Hello, " ~ name.content ~ "!"); }); // show the window and run the event loop until this window is closed window.loop(); }
To compile, run opend hello.d, then run the generated hello program.
While the specifics will change, nearly all minigui applications will roughly follow this pattern.
You may call this if you don't have a single main window.
Even a basic minigui window can benefit from these if you don't have a single main window:
import arsd.minigui; void main() { // create a struct to hold gathered info struct Hello { string name; } // let minigui create a dialog box to get that // info from the user. If you have a main window, // you'd pass that here, but it is not required dialog((Hello info) { // inline handler of the "OK" button messageBox("Hello, " ~ info.name); }); // since there is no main window to loop on, // we instead call the event loop singleton ourselves EventLoop.get.run; }
This is also useful when your programs lives as a notification area (aka systray) icon instead of as a window. But let's not get too far ahead of ourselves!
To better understand the details of layout algorithms and see more available included classes, see Layout.
minigui windows default to a flexible vertical layout, where widgets are added, from top to bottom on the window, in the same order of you creating them, then they are sized according to layout hints on the widget itself to fill the available space. This gives a reasonably usable setup but you'll probably want to customize it.
Generally speaking, there are two ways to customize layouts: either subclass the widget and change its hints, or wrap it in another layout widget. You can also create your own layout classes and do it all yourself, but that's fairly complicated. Wrapping existing widgets in other layout widgets is usually the easiest way to make things work.
For example, to display two widgets side-by-side, you can wrap them in a HorizontalLayout:
import arsd.minigui; void main() { auto window = new MainWindow(); // make the layout a child of our window auto hl = new HorizontalLayout(window); // then make the widgets children of the layout auto leftButton = new Button("Left", hl); auto rightButton = new Button("Right", hl); window.loop(); }
A HorizontalLayout works just like the default VerticalLayout, except in the other direction. These two buttons will take up all the available vertical space, then split available horizontal space equally.
Nesting layouts lets you carve up the rectangle in different ways.
This example shows one way you can partition your window into a header and sidebar. Here, the header and sidebar have a fixed width, while the rest of the content sizes with the window.
It might be a new way of thinking about window layout to do things this way - perhaps GridLayout more matches your style of thought - but the concept here is to partition the window into sub-boxes with a particular size, then partition those boxes into further boxes.
So to make the header, start with a child layout that has a max height. It will use that space from the top, then the remaining children will split the remaining area, meaning you can think of is as just being another box you can split again. Keep splitting until you have the look you desire.
import arsd.minigui; // This helper class is just to help make the layout boxes visible. // think of it like a <div style="background-color: whatever;"></div> in HTML. class ColorWidget : Widget { this(Color color, Widget parent) { this.color = color; super(parent); } Color color; class Style : Widget.Style { override WidgetBackground background() { return WidgetBackground(color); } } mixin OverrideStyle!Style; } void main() { auto window = new Window; // the key is to give it a max height. This is one way to do it: auto header = new class HorizontalLayout { this() { super(window); } override int maxHeight() { return 50; } }; // this next line is a shortcut way of doing it too, but it only works // for HorizontalLayout and VerticalLayout, and is less explicit, so it // is good to know how to make a new class like above anyway. // auto header = new HorizontalLayout(50, window); auto bar = new HorizontalLayout(window); // or since this is so common, VerticalLayout and HorizontalLayout both // can just take an argument in their constructor for max width/height respectively // (could have tone this above too, but I wanted to demo both techniques) auto left = new VerticalLayout(100, bar); // and this is the main section's container. A plain Widget instance is good enough here. auto container = new Widget(bar); // and these just add color to the containers we made above for the screenshot. // in a real application, you can just add your actual controls instead of these. auto headerColorBox = new ColorWidget(Color.teal, header); auto leftColorBox = new ColorWidget(Color.green, left); auto rightColorBox = new ColorWidget(Color.purple, container); window.loop(); }
TabWidget can show pages of layouts as tabs.
See ScrollableWidget but be warned that it is weird. You might want to consider something like GenericListViewWidget instead.
HorizontalLayout, VerticalLayout, InlineBlockLayout, GridLayout
To better understanding the underlying event system, see Event.
Each widget emits its own events, which propagate up through their parents until they reach their top-level window.
We started this series with a MainWindow, but only added widgets to it. MainWindows also support menus and toolbars with various keyboard shortcuts. You can construct these menus by constructing classes and calling methods, but minigui also lets you just write functions in a command object and it does the rest!
See MainWindow.setMenuAndToolbarFromAnnotatedCode for an example.
Note that toggleable menu or toolbar items are not yet implemented, but on the todolist. Submenus and disabled items are also not supported at this time and not currently on the work list (but if you need it, let me know and MAYBE we can work something out. Emphasis on maybe).
All windows also have titles. You can change this at any time with the window.title = "string"; property.
Windows also have icons, which can be set with the window.icon property. It takes a arsd.color.MemoryImage object, which is an in-memory bitmap. arsd.image can load common file formats into these objects, or you can make one yourself. The default icon on Windows is the icon of your exe, which you can set through a resource file. (FIXME: explain how to do this easily.)
The MainWindow also provides a status bar across the bottom. These aren't so common in new applications, but I love them - on my own computer, I even have a global status bar for my whole desktop! I suggest you use it: a status bar is a consistent place to put information and notifications that will never overlap other content.
A status bar has parts, and the parts have content. The first part's content is assumed to change frequently; the default mouse over event will set it to Widget.statusTip, a public string you can assign to any widget you want at any time.
Other parts can be added by you and are under your control. You add them with:
window.statusBar.parts ~= StatusBar.Part(optional_size, optional_units);
The size can be in a variety of units and what you get with mixes can get complicated. The rule is: explicit pixel sizes are used first. Then, proportional sizes are applied to the remaining space. Then, finally, if there is any space left, any items without an explicit size split them equally.
You may prefer to set them all at once, with:
window.statusBar.parts.setSizes(1, 1, 1);
This makes a three-part status bar, each with the same size - they all take the same proportion of the total size. Negative numbers here will use auto-scaled pixels.
You should call this right after creating your MainWindow as part of your setup code.
Once you make parts, you can explicitly change their content with window.statusBar.parts[index].content = "some string";
If you really don't want a status bar on your main window, you can remove it with window.statusBar = null; Make sure you don't try to use it again, or your program will likely crash!
Status bars, at this time, cannot hold non-text content, but I do want to change that. They also cannot have event listeners at this time, but again, that is likely to change. I have something in mind where they can hold clickable messages with a history and maybe icons, but haven't implemented any of that yet. Right now, they're just a (still very useful!) display area.
Minigui's custom widgets support styling parameters on the level of individual widgets, or application-wide with VisualThemes.
At this time, custom widgets gain capability in styling, but lose capability in terms of keeping all the right integrated details of the user experience and availability to accessibility and other automation tools. Evaluate if the benefit is worth the costs before making your decision.
I'd like to erase more and more of these gaps, but no promises as to when - or even if - that will ever actually happen.
See Widget.Style for more information.
And more. See members until I write up more of this later and also be aware of the package arsd.minigui_addons.
If none of these do what you need, you'll want to write your own. More on that in the following section.
See Widget.
If you override Widget.recomputeChildLayout, don't forget to call registerMovement() at the top of it, then call recomputeChildLayout of all its children too!
If you need a nested OS level window, see NestedChildWindowWidget. Use Widget.scaleWithDpi to convert logical pixels to physical pixels, as required.
See Widget.OverrideStyle, Widget.paintContent, Widget.dynamicState for some useful starting points.
You may also want to provide layout and style hints by overriding things like Widget.flexBasisWidth, Widget.flexBasisHeight, Widget.minHeight, yada, yada, yada.
You might make a compound widget out of other widgets. Widget.encapsulatedChildren can help hide this from the outside world (though is not necessary and might hurt some debugging!)
The Timer class is available and you can call widget.redraw(); to trigger a redraw from a timer handler.
I generally don't like animations in my programs, so it hasn't been a priority for me to do more than this. I also hate uis that move outside of explicit user action, so minigui kinda supports this but I'd rather you didn't. I kinda wanna do something like requestAnimationFrame or something but haven't yet so it is just the Timer class.
GUI application users tend to expect integration with their system, so clipboard support is basically a must, and drag and drop is nice to offer too. The functions for these are provided in arsd.simpledisplay, which is public imported from minigui, and thus available to you here too.
I'd like to think of some better abstractions to make this more automagic, but you must do it yourself when implementing your custom widgets right now.
See: draggable, DropHandler, setClipboardText, setClipboardImage, getClipboardText, getClipboardImage, setPrimarySelection, and others from simpledisplay.
Override Widget.contextMenu in your subclass.
Among the unfinished features: unified selections, translateable strings, external integrations.
Note the environment variable ARSD_SCALING_FACTOR on Linux can set multi-monitor scaling factors. I should also read it from a root window property so it easier to do with migrations... maybe a default theme selector from there too.
minigui's only required dependencies are arsd.simpledisplay, arsd.color, and arsd.textlayouter, on which it is built. simpledisplay provides the low-level interfaces and minigui builds the concept of widgets inside the windows on top of it.
Its #1 goal is to be useful without being large and complicated like GTK and Qt. It isn't hugely concerned with appearance - on Windows, it just uses the native controls and native theme, and on Linux, it keeps it simple and I may change that at any time, though after May 2021, you can customize some things with css-inspired Widget.Style classes. (On Windows, if you compile with -version=custom_widgets, you can use the custom implementation there too, but... you shouldn't.)
The event model is similar to what you use in the browser with Javascript and the layout engine tries to automatically fit things in, similar to a css flexbox.
FOR BEST RESULTS: be sure to link with the appropriate subsystem command -L/SUBSYSTEM:WINDOWS and -L/entry:mainCRTStartup`. If using ldc instead of dmd, use -L/entry:wmainCRTStartup instead of mainCRTStartup; note the "w".
Otherwise you'll get a console and possibly other visual bugs. But if you do use the subsystem:windows, note that Phobos' writeln will crash the program!
HTML Code | Minigui Class |
---|---|
<input type="text"> | LineEdit |
<input type="password"> | PasswordEdit |
<textarea> | TextEdit |
<select> | DropDownSelection |
<input type="checkbox"> | Checkbox |
<input type="radio"> | Radiobox |
<button> | Button |
Stretchiness: The default is 4. You can use larger numbers for things that should consume a lot of space, and lower numbers for ones that are better at smaller sizes.
COMING EVENTUALLY: minigui will include a little bit of I/O functionality that just works with the event loop. If you want to get fancy, I suggest spinning up another thread and posting events back and forth.
See the minigui_addons directory in the arsd repo for some add on widgets you can import separately too.
If you use arsd.minigui_xml, you can create widget trees from XML at runtime.
minigui is compatible with arsd.script. If you see @scriptable on a method in this documentation, it means you can call it from the script language.
Tip: to allow easy creation of widget trees from script, import arsd.minigui_xml and make arsd.minigui_xml.makeWidgetFromString available to your script:
import arsd.minigui_xml; import arsd.script; var globals = var.emptyObject; globals.makeWidgetFromString = &makeWidgetFromString; // this now works interpret(`var window = makeWidgetFromString("<MainWindow />");`, globals);
More to come.
Note that the Linux custom widgets generally aim to be efficient on remote X network connections.
In a perfect world, you'd achieve all the following goals:
I want to do some newer ideas that might not be easy to keep working fully on Windows, like adding a menu search feature and scrollbar custom marks and typing in numbers. I might make them a default part of the widget with custom, and let you provide them through a menu or something elsewhere.
This hello world sample will have an oversized button, but that's ok, you see your first window!
import arsd.minigui; void main() { auto window = new MainWindow(); // note the parent widget is almost always passed as the last argument to a constructor auto hello = new TextLabel("Hello, world!", TextAlignment.Center, window); auto button = new Button("Close", window); button.addWhenTriggered({ window.close(); }); window.loop(); }
Minigui had mostly additive changes or bug fixes since its inception until May 2021.
In May 2021 (dub v10.0), minigui got an overhaul. If it was versioned independently, I'd tag this as version 2.0.
Among the changes:
See Event for details.
See DoubleClickEvent for details.
See Widget.Style for details.
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 I'm slowly working on it).