├── LICENSE ├── README.md ├── package.json ├── resources └── poison │ ├── fonts │ ├── verdana.ttf │ ├── verdanab.ttf │ ├── verdanai.ttf │ └── verdanaz.ttf │ └── styles │ ├── android.pss │ ├── ios.pss │ ├── linux.pss │ ├── osx.pss │ ├── poison.pss │ ├── win-10.pss │ ├── win-7.pss │ ├── win-8.pss │ ├── win-vista.pss │ └── win-xp.pss └── src ├── core ├── action.d ├── application.d ├── eventargs │ ├── buttoneventargs.d │ ├── changeeventargs.d │ ├── keyeventargs.d │ ├── mouseeventargs.d │ ├── package.d │ └── texteventargs.d ├── eventhandler.d ├── eventobserver.d ├── exceptions │ ├── crossthreadingexception.d │ └── package.d ├── location.d ├── package.d ├── threading.d └── vector.d ├── debugging ├── log.d └── package.d ├── main.d └── ui ├── component.d ├── container.d ├── controls ├── package.d └── picturebox.d ├── fonts.d ├── graphics.d ├── package.d ├── paint.d ├── picture.d ├── space.d ├── sprite.d ├── styles.d └── window.d /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 PoisonEngine 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | Dub version 3 | 4 | 5 | 6 | homepage 7 | 8 | 9 | # Poison - The high performance cross-platform desktop/mobile UI engine 10 | Poison is a high performance cross-platform desktop/mobile UI engine written in D using dsfml. 11 | It's based on the idea of having a UI library that is compatible with games and multithreaded applications. 12 | 13 | Generally it's hard to write UI's for games or handle UI's with highly multithreaded applications, but the core 14 | of Poison manages that very well. 15 | 16 | ## Why another UI engine? 17 | Poison was born, because right now there isn't any really nice choice to render UI's in D when it comes to game development. There are plenty UI libraries, but most are for native rendering and not through libraries such as sfml, which makes it really hard to implement UI's with those for ex. games written in dsfml. 18 | 19 | ## Why not extend existing libraries to support dsfml then? 20 | Poison is being used in a game project and by having developed the UI engine ourselves, we have full control of how the rendering goes, meaning we could optimize it how we wanted and have it fit to our games need. It also had us apply a sense of multithreading/concurrency pattern from the very beginning as it would be hard to apply that to an existing library without breaking changes. At the end of the day, writing a new library/engine was just much easier. 21 | 22 | ## How do I use Poison on mobile platforms such as Android or iOS? 23 | At the moment mobile support hasn't been made, but as soon as desktop makes its first stable-round then it's all about making Poison compile to mobile platforms, create stylesheets and it should be done, since the engine should work on all platforms without major modification. 24 | 25 | ## Styling 26 | Styling components with Poison has been made easy with a json-like/css-like syntax and properties. 27 | 28 | The syntax is simple. 29 | 30 | First you put a selector. There are 3 types of selectors. 31 | 32 | * Class selectors - Generally there exist a class for every component. (A selector without prefix) 33 | * Identifier selectors (A selector with # as prefix) 34 | * State selectors - Must be combined with one of the two selectors above. Used to style depending on states ex. when a component is enabled or disabled. (A suffix to a selector like "selector:state") 35 | 36 | Secondly you create a scope of properties that has values. Values may either be a string or an array of strings. 37 | The strings should consist of either groups of values or a single group with values. 38 | 39 | A group separates its values with ";" and groups are separated with "|" 40 | 41 | ### Examples 42 | Syntax Example: 43 | ``` 44 | "selector" { 45 | "property": "value", 46 | "property2": "value" 47 | }, 48 | "selector2" { 49 | "property": "value" 50 | } 51 | ``` 52 | 53 | Example for styling a window. 54 | ``` 55 | "window": { 56 | "background-color": "238;238;238" 57 | }, 58 | "window:disabled": { 59 | "background-color": "255;0;0" 60 | } 61 | ``` 62 | 63 | ### Styling Properties (";" is a value separator :: "|" is a group separator) 64 | 65 | * background (Done) 66 | * value: "color: value|image: value" 67 | * background-color (Done) 68 | * value: "R;G;B;A" 69 | * value: "R;G;B" 70 | * value: "#hex" 71 | * value: "colorName" 72 | * background-image (Done) 73 | * value: "path" 74 | * value: "base64:base64_here" 75 | * foreground-color (Done) 76 | * value: "R;G;B;A" 77 | * value: "R;G;B" 78 | * value: "#hex" 79 | * value: "colorName" 80 | * font (Done) 81 | * value: "name: value|path: value|size: value" 82 | * font-name (Font's must be loaded upfront for font-name to work.) (Done) 83 | * value: "name;style" 84 | * font-path (Path can also be a font-name with/without the style suffixes ("i", "b" or "z"), but most be loaded upfront to work.) (Done) 85 | * value: "path" 86 | * font-size (Done) 87 | * value: "size" 88 | * paint-color (Array of values) (Done) 89 | * value: "position: value|size: value|color: value" 90 | * paint-gradient-hoz / paint-gradient-ver (Array of values) (Done) 91 | * value: "position: value|size: value|fromColor: value|toColor: value" 92 | * border / border-top / border-right / border-bottom / border-left 93 | * value: "size;color" 94 | * value: "style:size;color" (Style can be normal or round) 95 | * size (Done) 96 | * value: "width;height" 97 | * margin 98 | * value: "top;right;bottom;left" 99 | * padding 100 | * value: "top;right;bottom;left" 101 | * position (Done) 102 | * value: "x;y" 103 | * layout 104 | * value: "layout" (Can be fit, horizontal, vertical) 105 | * dock 106 | * value: "dockedPosition" (Can be fill, top, right, bottom, left, centerX, centerY, center) 107 | 108 | ## How do I use poison? 109 | There will be posted a few guides in the wiki soon, but for now keep an eye open as it's still in its early alpha-phase. Which means nothing is stable as it is and components are still being made. 110 | 111 | A lot of stuff can change until we reach our first revision. 112 | 113 | ## How do I contribute? 114 | Keep your eyes open for updates, there'll soon be a contribution guide. As for now you can fork the project and simple create pull requests with whatever contributions you have. We're not so strict as it is right now. 115 | 116 | Project management, issue tracking and guides: 117 | 118 | https://tree.taiga.io/project/poisonengine-poison-ui/ 119 | 120 | ## Widgets/Components 121 | 122 | ### Misc (poison.ui.controls) 123 | 124 | * PictureBox (Done) 125 | * Label 126 | * Button 127 | * SplitButton 128 | * Spacer 129 | * Mask 130 | * Group 131 | * ScrollBar 132 | * ToolBar 133 | * TitleBar 134 | * ProgressBar 135 | * Splitter 136 | * Strip 137 | * Clock 138 | * MessageBox 139 | 140 | ### Panels (poison.ui.panels) 141 | 142 | * Panel 143 | * FormPanel (Alias to poison.ui.forms.formpanel.FormPanel) 144 | * TabPanel 145 | * NavigationPanel 146 | * TreePanel 147 | * WizardPanel 148 | 149 | ### Sheets (poison.ui.sheets) 150 | 151 | * Sheet 152 | * ActionSheet 153 | * PushMenu (Alias to poison.ui.menus.pushmenu.PushMenu) 154 | 155 | ### Pickers (poison.ui.pickers) 156 | 157 | * Picker 158 | * DateTimePicker 159 | * ColorPicker 160 | * SelectPicker 161 | 162 | ### Forms (poison.ui.forms) 163 | 164 | * FormPanel 165 | * Fieldset 166 | 167 | ### Fields (poison.ui.fields) 168 | 169 | * Field 170 | * TextField 171 | * CheckBoxField 172 | * RadioField 173 | * ToggleField 174 | * SelectField 175 | * NumberField 176 | * FileField 177 | * SearchField 178 | * SliderField 179 | * SpinnerField 180 | * EmailField 181 | 182 | ### DataViews (poison.ui.dataviews) 183 | 184 | * DataView 185 | * List 186 | * NestedList 187 | * Grid (Alias to poison.ui.grids.grid.Grid) 188 | 189 | ### Grids (poison.ui.grids) 190 | 191 | * Grid 192 | * ArrayGrid 193 | * GroupedGrid 194 | * LockedGrid 195 | 196 | ### Menus (poison.ui.menu) 197 | 198 | * ContextMenu 199 | * PushMenu 200 | * ToolMenu 201 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "poison", 3 | "description": "The high performance cross-platform desktop/mobile UI engine.", 4 | "authors": [ 5 | "Jacob Jensen" 6 | ], 7 | "license": "The MIT License (MIT)", 8 | "copyright": "Copyright © 2016 Poison Engine", 9 | "targetType": "sourceLibrary", 10 | "sourcePaths": ["src"], 11 | "copyFiles": ["resources"], 12 | "homepage": "https://poisonengine.github.io/poison-ui/", 13 | "dependencies": { 14 | "dsfml": { 15 | "version": "~>2.1.1" 16 | } 17 | }, 18 | "copyFiles": ["resources"] 19 | } 20 | -------------------------------------------------------------------------------- /resources/poison/fonts/verdana.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PoisonEngine/poison-ui/745272c3108991dfe6bae4e9029527802d722c4e/resources/poison/fonts/verdana.ttf -------------------------------------------------------------------------------- /resources/poison/fonts/verdanab.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PoisonEngine/poison-ui/745272c3108991dfe6bae4e9029527802d722c4e/resources/poison/fonts/verdanab.ttf -------------------------------------------------------------------------------- /resources/poison/fonts/verdanai.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PoisonEngine/poison-ui/745272c3108991dfe6bae4e9029527802d722c4e/resources/poison/fonts/verdanai.ttf -------------------------------------------------------------------------------- /resources/poison/fonts/verdanaz.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PoisonEngine/poison-ui/745272c3108991dfe6bae4e9029527802d722c4e/resources/poison/fonts/verdanaz.ttf -------------------------------------------------------------------------------- /resources/poison/styles/android.pss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PoisonEngine/poison-ui/745272c3108991dfe6bae4e9029527802d722c4e/resources/poison/styles/android.pss -------------------------------------------------------------------------------- /resources/poison/styles/ios.pss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PoisonEngine/poison-ui/745272c3108991dfe6bae4e9029527802d722c4e/resources/poison/styles/ios.pss -------------------------------------------------------------------------------- /resources/poison/styles/linux.pss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PoisonEngine/poison-ui/745272c3108991dfe6bae4e9029527802d722c4e/resources/poison/styles/linux.pss -------------------------------------------------------------------------------- /resources/poison/styles/osx.pss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PoisonEngine/poison-ui/745272c3108991dfe6bae4e9029527802d722c4e/resources/poison/styles/osx.pss -------------------------------------------------------------------------------- /resources/poison/styles/poison.pss: -------------------------------------------------------------------------------- 1 | "window": { 2 | "background-color": "#eee" 3 | }, 4 | "window:disabled": { 5 | "background-color": "cornflowerBlue" 6 | } 7 | -------------------------------------------------------------------------------- /resources/poison/styles/win-10.pss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PoisonEngine/poison-ui/745272c3108991dfe6bae4e9029527802d722c4e/resources/poison/styles/win-10.pss -------------------------------------------------------------------------------- /resources/poison/styles/win-7.pss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PoisonEngine/poison-ui/745272c3108991dfe6bae4e9029527802d722c4e/resources/poison/styles/win-7.pss -------------------------------------------------------------------------------- /resources/poison/styles/win-8.pss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PoisonEngine/poison-ui/745272c3108991dfe6bae4e9029527802d722c4e/resources/poison/styles/win-8.pss -------------------------------------------------------------------------------- /resources/poison/styles/win-vista.pss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PoisonEngine/poison-ui/745272c3108991dfe6bae4e9029527802d722c4e/resources/poison/styles/win-vista.pss -------------------------------------------------------------------------------- /resources/poison/styles/win-xp.pss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PoisonEngine/poison-ui/745272c3108991dfe6bae4e9029527802d722c4e/resources/poison/styles/win-xp.pss -------------------------------------------------------------------------------- /src/core/action.d: -------------------------------------------------------------------------------- 1 | /** 2 | * Module for actions. Actions are classes that wraps around a function pointer or delegate. 3 | * 4 | * Authors: 5 | * Jacob Jensen 6 | * License: 7 | * https://github.com/PoisonEngine/poison-ui/blob/master/LICENSE 8 | */ 9 | module poison.core.action; 10 | 11 | /// An action. 12 | class Action { 13 | private: 14 | /// The function pointer. 15 | void function() _f; 16 | 17 | /// The delegate. 18 | void delegate() _d; 19 | 20 | public: 21 | /** 22 | * Creates a new instance of the action passing a function pointer. 23 | * Params: 24 | * f = The function pointer. 25 | */ 26 | this(void function() f) { 27 | _f = f; 28 | } 29 | 30 | 31 | /** 32 | * Creates a new instance of the action passing a delegate. 33 | * Params: 34 | * d = The delegate. 35 | */ 36 | this(void delegate() d) { 37 | _d = d; 38 | } 39 | 40 | /// Operator overload for calling Action implicit. 41 | void opCall() { 42 | if (_f) { 43 | _f(); 44 | } 45 | else if (_d) { 46 | _d(); 47 | } 48 | } 49 | } 50 | 51 | /// An action that takes a generic argument. 52 | class ActionArgs(T) { 53 | private: 54 | /// The function pointer. 55 | void function(T) _f; 56 | 57 | /// The delegate. 58 | void delegate(T) _d; 59 | 60 | public: 61 | /** 62 | * Creates a new instance of the action passing a function pointer. 63 | * Params: 64 | * f = The function pointer. 65 | */ 66 | this(void function(T) f) { 67 | _f = f; 68 | } 69 | 70 | /** 71 | * Creates a new instance of the action passing a delegate. 72 | * Params: 73 | * d = The delegate. 74 | */ 75 | this(void delegate(T) d) { 76 | _d = d; 77 | } 78 | 79 | /** 80 | * Operator overload for calling Action implicit. 81 | * Params: 82 | * arg = The argument to pass. 83 | */ 84 | void opCall(T arg) { 85 | if (_f) { 86 | _f(arg); 87 | } 88 | else if (_d) { 89 | _d(arg); 90 | } 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /src/core/application.d: -------------------------------------------------------------------------------- 1 | /** 2 | * Module for core application handling. 3 | * 4 | * Authors: 5 | * Jacob Jensen 6 | * License: 7 | * https://github.com/PoisonEngine/poison-ui/blob/master/LICENSE 8 | */ 9 | module poison.core.application; 10 | 11 | import std.concurrency : thisTid; 12 | import core.thread : Thread, dur; 13 | 14 | import poison.ui : Window; 15 | import poison.core.threading : _uiTid, receiveMessages; 16 | import poison.core.eventobserver; 17 | import poison.core.eventargs; 18 | 19 | /// A wrapper around the core application. 20 | class Application { 21 | private: 22 | /// The running application. 23 | static Application _app; 24 | 25 | /// The name of the application. 26 | string _name; 27 | 28 | /// Collection of windows. 29 | Window[string] _windows; 30 | 31 | /// Windows that can be removed. 32 | string[] _removableWindows; 33 | 34 | /// Boolean determining whether the application is cycling or not. 35 | bool _cycling; 36 | 37 | /// Boolean determining whether the application is open or not. 38 | bool _open; 39 | 40 | public: 41 | /** 42 | * Creates a new application. 43 | * Params: 44 | * name = The name of the application. 45 | */ 46 | this(string name) { 47 | _name = name; 48 | _uiTid = thisTid; 49 | } 50 | 51 | @property { 52 | /// Gets the name of the application. 53 | string name() { return _name; } 54 | } 55 | 56 | /// Updates all styles for each window. 57 | void updateStyles() { 58 | if (_windows) { 59 | foreach (window; _windows) { 60 | foreach (component; window._windowComponents) { 61 | component.updateStyles(); 62 | } 63 | } 64 | } 65 | } 66 | 67 | /** 68 | * Adds a window to the application. 69 | * Params: 70 | * window = The window to add. 71 | */ 72 | void add(Window window) { 73 | assert(window !is null); 74 | assert(_windows.get(window.name, null) is null); 75 | 76 | _windows[window.name] = window; 77 | 78 | foreach (component; window._windowComponents) { 79 | component.updateStyles(); 80 | } 81 | } 82 | 83 | /** 84 | * Removes a window from the application. 85 | * Params: 86 | * window = The window to remove. 87 | */ 88 | void remove(Window window) { 89 | assert(window !is null); 90 | 91 | remove(window.name); 92 | } 93 | 94 | /** 95 | * Removes a window from the application. 96 | * Params: 97 | * name = The name of the window to remove. 98 | */ 99 | void remove(string name) { 100 | assert(_windows.get(name, null) !is null); 101 | 102 | if (_cycling) { 103 | _removableWindows ~= name; 104 | } 105 | else { 106 | auto windowToRemove = _windows.get(name, null); 107 | 108 | if (!windowToRemove) { 109 | return; 110 | } 111 | 112 | if (windowToRemove.isOpen) { 113 | windowToRemove.close(); 114 | } 115 | 116 | _windows.remove(name); 117 | } 118 | } 119 | 120 | private: 121 | /// Processes the application. 122 | void process() { 123 | assert(_app !is null); 124 | 125 | _open = true; 126 | 127 | while (_open) { 128 | _removableWindows = []; 129 | _cycling = true; 130 | 131 | receiveMessages(); 132 | 133 | processWindows(); 134 | 135 | _cycling = false; 136 | 137 | foreach (removableWindow; _removableWindows) { 138 | remove(removableWindow); 139 | } 140 | } 141 | } 142 | 143 | /// Processes all windows. 144 | void processWindows() { 145 | _open = false; 146 | 147 | foreach (window; _windows) { 148 | if (window.isOpen) { 149 | _open = true; 150 | 151 | window.process(); 152 | } 153 | } 154 | } 155 | 156 | public: 157 | static: 158 | /** 159 | * Initializes a application and then processes it. 160 | * Params: 161 | * application = The application to initialize and process. 162 | */ 163 | void initialize(Application application) { 164 | assert(_app is null); 165 | 166 | _app = application; 167 | 168 | _app.updateStyles(); 169 | EventObserver.fireEventGlobal("applicationStart", EventArgs.empty); 170 | _app.process(); 171 | EventObserver.fireEventGlobal("applicationEnd", EventArgs.empty); 172 | } 173 | 174 | @property { 175 | /// Gets the app. 176 | Application app() { return _app; } 177 | } 178 | } 179 | -------------------------------------------------------------------------------- /src/core/eventargs/buttoneventargs.d: -------------------------------------------------------------------------------- 1 | /** 2 | * A module for button event args handling. 3 | * 4 | * Authors: 5 | * Jacob Jensen 6 | * License: 7 | * https://github.com/PoisonEngine/poison-ui/blob/master/LICENSE 8 | */ 9 | module poison.core.eventargs.buttoneventargs; 10 | 11 | import std.algorithm : filter; 12 | import std.array : array; 13 | 14 | import poison.core.eventargs : EventArgs; 15 | 16 | // TODO: handle double click by time checking ... 17 | 18 | 19 | /// Event args that relies on button logic. 20 | class ButtonEventArgs(T) : EventArgs { 21 | private: 22 | /// The currently pressed buttons. 23 | T[] _pressed; 24 | 25 | /// The last pressed button. 26 | T _lastPressed; 27 | 28 | /// The current pressed button. 29 | T _currentPressed; 30 | 31 | protected: 32 | /// Creates a new button event args wrapper. 33 | this() { 34 | super(); 35 | } 36 | 37 | public: 38 | /** 39 | * Presses a button. 40 | * Params: 41 | * button = The button to press. 42 | */ 43 | void press(T button) { 44 | if (isPressed(button)) { 45 | return; 46 | } 47 | 48 | _pressed ~= button; 49 | _lastPressed = _currentPressed; 50 | _currentPressed = button; 51 | } 52 | 53 | /** 54 | * Releases a button. 55 | * Params: 56 | * button = The button to release. 57 | */ 58 | void release(T button) { 59 | _pressed = _pressed.filter!((b) { return b != button; }).array; 60 | _lastPressed = _currentPressed; 61 | } 62 | 63 | /** 64 | * Checks whether a specific button is pressed or not. 65 | * Params: 66 | * pressedButton = The button to check if pressed or not. 67 | * Returns: 68 | * true if the button is pressed. 69 | */ 70 | bool isPressed(T pressedButton) { 71 | if (_currentPressed == pressedButton) { 72 | return true; 73 | } 74 | 75 | foreach (button; _pressed) { 76 | if (button == pressedButton) { 77 | return true; 78 | } 79 | } 80 | 81 | return false; 82 | } 83 | 84 | /** 85 | * Checks whether a range of buttons are pressed or not. 86 | * Params: 87 | * pressedButtons = The buttons to check if pressed or not. 88 | * Returns: 89 | * true if all buttons are pressed. 90 | */ 91 | bool isPressed(Range)(Range pressedButtons) { 92 | assert(pressedButtons !is null); 93 | 94 | foreach (button; pressedButtons) { 95 | if (!isPressed(button)) { 96 | return false; 97 | } 98 | } 99 | 100 | return true; 101 | } 102 | 103 | @property { 104 | /// Gets all buttons currently pressed. 105 | T[] pressed() { return _pressed; } 106 | 107 | /// Gets the last button pressed. 108 | T lastPressed() { return _lastPressed; } 109 | 110 | /// Gets the current button pressed. 111 | T currentPressed() { return _currentPressed; } 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /src/core/eventargs/changeeventargs.d: -------------------------------------------------------------------------------- 1 | /** 2 | * Module for change event args handling. 3 | * 4 | * Authors: 5 | * Jacob Jensen 6 | * License: 7 | * https://github.com/PoisonEngine/poison-ui/blob/master/LICENSE 8 | */ 9 | module poison.core.eventargs.changeeventargs; 10 | 11 | import poison.core.eventargs : EventArgs; 12 | 13 | /// Event args for value changes. 14 | class ChangeEventArgs(T) : EventArgs { 15 | private: 16 | /// The old value. 17 | T _oldValue; 18 | 19 | /// The new value. 20 | T _newValue; 21 | 22 | public: 23 | /** 24 | * Creates a new change event args. 25 | * Params: 26 | * oldValue = The old value. 27 | * newValue = The new value. 28 | */ 29 | this(T oldValue, T newValue) { 30 | super(); 31 | 32 | _oldValue = oldValue; 33 | _newValue = newValue; 34 | } 35 | 36 | @property { 37 | /// Gets the old value. 38 | T oldValue() { return _oldValue; } 39 | 40 | /// Gets the new value. 41 | T newValue() { return _newValue; } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/core/eventargs/keyeventargs.d: -------------------------------------------------------------------------------- 1 | /** 2 | * Module for key event args handling. 3 | * 4 | * Authors: 5 | * Jacob Jensen 6 | * License: 7 | * https://github.com/PoisonEngine/poison-ui/blob/master/LICENSE 8 | */ 9 | module poison.core.eventargs.keyeventargs; 10 | 11 | import dsfml.window : Keyboard; 12 | 13 | public alias Key = Keyboard.Key; 14 | 15 | import poison.core.eventargs.buttoneventargs; 16 | 17 | /// Event args for key events. 18 | class KeyEventArgs : ButtonEventArgs!Key { 19 | package(poison): 20 | /// Creates a new instance key event args. 21 | this() { 22 | super(); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/core/eventargs/mouseeventargs.d: -------------------------------------------------------------------------------- 1 | /** 2 | * Module for mouse event args handling. 3 | * 4 | * Authors: 5 | * Jacob Jensen 6 | * License: 7 | * https://github.com/PoisonEngine/poison-ui/blob/master/LICENSE 8 | */ 9 | module poison.core.eventargs.mouseeventargs; 10 | 11 | import dsfml.window : Mouse; 12 | 13 | public alias MouseButton = Mouse.Button; 14 | 15 | import poison.core.eventargs.buttoneventargs; 16 | import poison.core.vector : Point; 17 | 18 | /// Event args for mouse events. 19 | class MouseEventArgs : ButtonEventArgs!MouseButton { 20 | private: 21 | /// The current position of the mouse. 22 | Point _position; 23 | 24 | public: 25 | @property { 26 | /// Gets the current position of the mouse. 27 | Point position() { return _position; } 28 | } 29 | 30 | package(poison): 31 | /// Creates a new instance of the mouse event args. 32 | this() { 33 | super(); 34 | } 35 | 36 | @property { 37 | /// Sets the position of the mouse. 38 | void position(Point newPosition) { 39 | _position = newPosition; 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/core/eventargs/package.d: -------------------------------------------------------------------------------- 1 | /** 2 | * Package module for event args. 3 | * 4 | * Authors: 5 | * Jacob Jensen 6 | * License: 7 | * https://github.com/PoisonEngine/poison-ui/blob/master/LICENSE 8 | */ 9 | module poison.core.eventargs; 10 | 11 | /// Base event args 12 | class EventArgs { 13 | /// The empty event args. 14 | private static EventArgs _empty; 15 | 16 | protected: 17 | /// Creates a new instance of event args. 18 | this() { 19 | 20 | } 21 | 22 | public: 23 | static: 24 | @property { 25 | /// Gets an empty event args. 26 | EventArgs empty() { 27 | if (!_empty) { 28 | _empty = new EventArgs(); 29 | } 30 | 31 | return _empty; 32 | } 33 | } 34 | } 35 | 36 | public { 37 | import poison.core.eventargs.changeeventargs; 38 | import poison.core.eventargs.buttoneventargs; 39 | import poison.core.eventargs.keyeventargs; 40 | import poison.core.eventargs.mouseeventargs; 41 | import poison.core.eventargs.texteventargs; 42 | } 43 | -------------------------------------------------------------------------------- /src/core/eventargs/texteventargs.d: -------------------------------------------------------------------------------- 1 | /** 2 | * Module for text event args handling. 3 | * 4 | * Authors: 5 | * Jacob Jensen 6 | * License: 7 | * https://github.com/PoisonEngine/poison-ui/blob/master/LICENSE 8 | */ 9 | module poison.core.eventargs.texteventargs; 10 | 11 | import poison.core.eventargs : EventArgs; 12 | 13 | /// Event args for text events. 14 | class TextEventArgs : EventArgs { 15 | private: 16 | /// The last character entered. 17 | dchar _last; 18 | 19 | /// The current character entered. 20 | dchar _current; 21 | 22 | public: 23 | @property { 24 | /// Gets the last character entered. 25 | dchar last() { return _last; } 26 | 27 | /// Gets the current character entered. 28 | dchar current() { return _current; } 29 | } 30 | 31 | /** 32 | * Enteres a character. 33 | * Params: 34 | * enteredChar = The entered character. 35 | */ 36 | void enter(dchar enteredChar) { 37 | _last = _current; 38 | _current = enteredChar; 39 | } 40 | 41 | package(poison): 42 | /// Creates a new instance of text event args. 43 | this() { 44 | super(); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/core/eventhandler.d: -------------------------------------------------------------------------------- 1 | /** 2 | * Module for event handlers. 3 | * 4 | * Authors: 5 | * Jacob Jensen 6 | * License: 7 | * https://github.com/PoisonEngine/poison-ui/blob/master/LICENSE 8 | */ 9 | module poison.core.eventhandler; 10 | 11 | import poison.core.eventargs; 12 | 13 | /// A base event handler. 14 | interface IBaseEventHandler { } 15 | 16 | /// An event handler. 17 | class EventHandler(TEventArgs : EventArgs) : IBaseEventHandler { 18 | private: 19 | /// The function pointer of the event handler. 20 | void function(TEventArgs) _f; 21 | 22 | /// The delegate of the event handler. 23 | void delegate(TEventArgs) _d; 24 | 25 | public: 26 | /** 27 | * Creates a new event handler. 28 | * Params: 29 | * f = The function pointer. 30 | */ 31 | this(void function(TEventArgs) f) { 32 | _f = f; 33 | } 34 | 35 | /** 36 | * Creates a new event handler. 37 | * Params: 38 | * d = The delegate. 39 | */ 40 | this(void delegate(TEventArgs) d) { 41 | _d = d; 42 | } 43 | 44 | /** 45 | * Operator overload for calling the event handler implicit. 46 | * Params: 47 | * e = The event args. 48 | */ 49 | void opCall(TEventArgs e) { 50 | if (_f) { 51 | _f(e); 52 | } 53 | else if (_d) { 54 | _d(e); 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/core/eventobserver.d: -------------------------------------------------------------------------------- 1 | /** 2 | * Module for event observation. 3 | * 4 | * Authors: 5 | * Jacob Jensen 6 | * License: 7 | * https://github.com/PoisonEngine/poison-ui/blob/master/LICENSE 8 | */ 9 | module poison.core.eventobserver; 10 | 11 | import poison.core.eventhandler; 12 | import poison.core.eventargs; 13 | 14 | /// An observer for events. 15 | class EventObserver { 16 | private: 17 | /// Global event handlers. 18 | static IBaseEventHandler[][string] _globalEventHandlers; 19 | 20 | /// Event handlers. 21 | IBaseEventHandler[][string] _eventHandlers; 22 | 23 | public: 24 | /** 25 | * Subscribes an event handler to an event. 26 | * Params: 27 | * eventName = The event to subscribe to. 28 | * handler = The event handler. 29 | */ 30 | void subscribe(TEventArgs : EventArgs)(string eventName, EventHandler!TEventArgs handler) { 31 | _eventHandlers[eventName] ~= handler; 32 | } 33 | 34 | /** 35 | * Unsubscribes an event. 36 | * Params: 37 | * eventName = The name of the event to unsubscribe. 38 | */ 39 | void unsubscribe(string eventName) { 40 | assert(eventName in _eventHandlers); 41 | 42 | _eventHandlers.remove(eventName); 43 | } 44 | 45 | /** 46 | * Fires an event. 47 | * Params: 48 | * eventName = The name of the event to fire. 49 | * eventArgs = The event args to pass. 50 | */ 51 | void fireEvent(TEventArgs : EventArgs)(string eventName, TEventArgs eventArgs) { 52 | auto handlers = _eventHandlers.get(eventName, null); 53 | 54 | if (handlers) { 55 | foreach (handler; handlers) { 56 | (cast(EventHandler!TEventArgs)handler)(eventArgs); 57 | } 58 | } 59 | } 60 | 61 | static: 62 | /** 63 | * Subscribes a global event handler to an event. 64 | * Params: 65 | * eventName = The event to subscribe to. 66 | * handler = The event handler. 67 | */ 68 | void subscribeGlobal(TEventArgs : EventArgs)(string eventName, EventHandler!TEventArgs handler) { 69 | _globalEventHandlers[eventName] ~= handler; 70 | } 71 | 72 | /** 73 | * Unsubscribes a global event. 74 | * Params: 75 | * eventName = The name of the event to unsubscribe. 76 | */ 77 | void unsubscribeGlobal(string eventName) { 78 | assert(eventName in _globalEventHandlers); 79 | 80 | _globalEventHandlers.remove(eventName); 81 | } 82 | 83 | /** 84 | * Fires a global event. 85 | * Params: 86 | * eventName = The name of the event to fire. 87 | * eventArgs = The event args to pass. 88 | */ 89 | void fireEventGlobal(TEventArgs : EventArgs)(string eventName, TEventArgs eventArgs) { 90 | auto handlers = _globalEventHandlers.get(eventName, null); 91 | 92 | if (handlers) { 93 | foreach (handler; handlers) { 94 | (cast(EventHandler!TEventArgs)handler)(eventArgs); 95 | } 96 | } 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /src/core/exceptions/crossthreadingexception.d: -------------------------------------------------------------------------------- 1 | /** 2 | * Module for cross threading exceptions. 3 | * 4 | * Authors: 5 | * Jacob Jensen 6 | * License: 7 | * https://github.com/PoisonEngine/poison-ui/blob/master/LICENSE 8 | */ 9 | module poison.core.exceptions.crossthreadingexception; 10 | 11 | import std.concurrency : Tid; 12 | import std.string : format; 13 | 14 | /// Exception thrown when attempting cross threading access in thread-bound contexts. 15 | class CrossThreadingExeption : Exception { 16 | private: 17 | /// The tid the thread was accessed from. 18 | Tid _tid; 19 | 20 | public: 21 | /** 22 | * Creates a new instance of the cross threading exception. 23 | * Params: 24 | * tid = The accessible tid. 25 | * msg = The message of the exception. 26 | */ 27 | this(Tid tid, string msg) { 28 | msg = format("Tid: %s\r\n%s", tid, msg); 29 | 30 | super(msg); 31 | 32 | _tid = tid; 33 | } 34 | 35 | @property { 36 | /// Gets the tid that the thread-bound context was accessed from. 37 | Tid tid() { return _tid; } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/core/exceptions/package.d: -------------------------------------------------------------------------------- 1 | /** 2 | * Package module for exceptions. 3 | * 4 | * Authors: 5 | * Jacob Jensen 6 | * License: 7 | * https://github.com/PoisonEngine/poison-ui/blob/master/LICENSE 8 | */ 9 | module poison.core.exceptions; 10 | 11 | public { 12 | import poison.core.exceptions.crossthreadingexception; 13 | } 14 | -------------------------------------------------------------------------------- /src/core/location.d: -------------------------------------------------------------------------------- 1 | /** 2 | * Module for location handling. 3 | * 4 | * Authors: 5 | * Jacob Jensen 6 | * License: 7 | * https://github.com/PoisonEngine/poison-ui/blob/master/LICENSE 8 | */ 9 | module poison.core.location; 10 | 11 | /// Enumeration of locations. 12 | enum Location { 13 | /// North west. 14 | northWest, 15 | /// North. 16 | north, 17 | /// North east. 18 | northEast, 19 | /// East. 20 | east, 21 | /// South east. 22 | southEast, 23 | /// South. 24 | south, 25 | /// South west. 26 | southWest, 27 | /// West. 28 | west, 29 | /// Center. 30 | center 31 | } 32 | -------------------------------------------------------------------------------- /src/core/package.d: -------------------------------------------------------------------------------- 1 | /** 2 | * Package module for the core modules. 3 | * 4 | * Authors: 5 | * Jacob Jensen 6 | * License: 7 | * https://github.com/PoisonEngine/poison-ui/blob/master/LICENSE 8 | */ 9 | module poison.core; 10 | 11 | public { 12 | import poison.core.application; 13 | import poison.core.vector; 14 | import poison.core.location; 15 | import poison.core.eventobserver; 16 | import poison.core.eventhandler; 17 | import poison.core.action; 18 | import poison.core.threading; 19 | 20 | import poison.core.eventargs; 21 | import poison.core.exceptions; 22 | } 23 | -------------------------------------------------------------------------------- /src/core/threading.d: -------------------------------------------------------------------------------- 1 | /** 2 | * Module for threading. 3 | * 4 | * Authors: 5 | * Jacob Jensen 6 | * License: 7 | * https://github.com/PoisonEngine/poison-ui/blob/master/LICENSE 8 | */ 9 | module poison.core.threading; 10 | 11 | import std.concurrency : send, receive, Tid, thisTid, receiveTimeout; 12 | import core.thread : dur; 13 | 14 | import poison.core.action; 15 | 16 | package(poison.core) { 17 | /// The tid of the ui thread. 18 | __gshared Tid _uiTid; 19 | } 20 | 21 | /// Gets a boolean determining whether the current thread is the ui thread. 22 | @property bool isUIThread() { 23 | return thisTid == _uiTid; 24 | } 25 | 26 | /** 27 | * Executes an action on the UI thread. 28 | * Params: 29 | * f = The function pointer to execute. 30 | */ 31 | void executeUI(void function() f) { 32 | executeUI(new Action(f)); 33 | } 34 | 35 | /** 36 | * Executes an action on the UI thread. 37 | * Params: 38 | * d = The delegate to execute. 39 | */ 40 | void executeUI(void delegate() d) { 41 | executeUI(new Action(d)); 42 | } 43 | 44 | /** 45 | * Executes an action on the UI thread. 46 | * Params: 47 | * action = The action to execute. 48 | */ 49 | void executeUI(Action action) { 50 | if (isUIThread) { 51 | action(); 52 | return; 53 | } 54 | 55 | send(_uiTid, cast(shared)action); 56 | } 57 | 58 | /** 59 | * Receives a concurrent message non-blocking. 60 | * Params: 61 | * ops = The message ops. 62 | */ 63 | private void receiveNonBlocking(T...)(T ops) { 64 | receiveTimeout( 65 | dur!("nsecs")(-1), 66 | ops 67 | ); 68 | } 69 | 70 | /// Receives all messages for the current thread and executes them. 71 | void receiveMessages() { 72 | receiveNonBlocking( 73 | (shared(Action) a) { (cast(Action)a)(); } 74 | ); 75 | } 76 | -------------------------------------------------------------------------------- /src/core/vector.d: -------------------------------------------------------------------------------- 1 | /** 2 | * Module for vectors. 3 | * 4 | * Authors: 5 | * Jacob Jensen 6 | * License: 7 | * https://github.com/PoisonEngine/poison-ui/blob/master/LICENSE 8 | */ 9 | module poison.core.vector; 10 | 11 | /** 12 | * Vector mixin template to create vector types. 13 | * Params: 14 | * T = The type of the vector. 15 | * names = The names of all members of the vector. 16 | */ 17 | private mixin template Vector(T, string[] names) { 18 | /// Format for members. 19 | enum memberFormat = q{ 20 | private %s _%s; 21 | }; 22 | 23 | /// Format for properties. 24 | enum propertyFormat = q{ 25 | @property { 26 | public auto %s() { return _%s; } 27 | 28 | public void %s(%s newValue) { _%s = newValue; } 29 | } 30 | }; 31 | 32 | /// Format for parameters. 33 | enum paramFormat = "%s %s,"; 34 | 35 | /// Format for member sets. 36 | enum memberSetFormat = "_%s = %s;"; 37 | 38 | /// Generates the constructor. 39 | static string generateConstructor() { 40 | import std.string : format; 41 | 42 | auto paramsString = ""; 43 | auto memberSetString = ""; 44 | 45 | foreach (name; names) { 46 | paramsString ~= paramFormat.format(T.stringof, name); 47 | 48 | memberSetString ~= memberSetFormat.format(name, name); 49 | } 50 | 51 | if (paramsString) { 52 | paramsString.length -= 1; 53 | } 54 | 55 | return "this(" ~ paramsString ~ ") { " ~ memberSetString ~ " }"; 56 | } 57 | 58 | mixin(generateConstructor); 59 | 60 | /// Generates the members. 61 | static string generateMembers() { 62 | import std.string : format; 63 | 64 | auto membersString = ""; 65 | 66 | foreach (name; names) { 67 | membersString ~= memberFormat.format(T.stringof, name); 68 | } 69 | 70 | return membersString; 71 | } 72 | 73 | mixin(generateMembers); 74 | 75 | /// Generates the properties. 76 | static string generateProperties() { 77 | import std.string : format; 78 | 79 | auto propertiesString = ""; 80 | 81 | foreach (name; names) { 82 | propertiesString ~= propertyFormat.format(name, name, name, T.stringof, name); 83 | } 84 | 85 | return propertiesString; 86 | } 87 | 88 | mixin(generateProperties); 89 | } 90 | 91 | /// A 2d point vector. 92 | private class Point2dVector(T) { 93 | mixin Vector!(T, ["x", "y"]); 94 | } 95 | 96 | /// Alias to create a 2d point vector of ptrdiff_t. 97 | public alias Point = Point2dVector!ptrdiff_t; 98 | 99 | /// Alias to create a 2d point vector of float. 100 | public alias PointF = Point2dVector!float; 101 | 102 | /// A 3d point vector. 103 | private class Point3dVector(T) { 104 | mixin Vector!(T, ["x", "y", "z"]); 105 | } 106 | 107 | /// Alias to create a 3d point vector of ptrdiff_t. 108 | public alias Point3d = Point3dVector!ptrdiff_t; 109 | 110 | /// Alias to create a 3d point vector of float. 111 | public alias Point3dF = Point3dVector!float; 112 | 113 | /// A 2d size vector. 114 | private class Size2dVector(T) { 115 | mixin Vector!(T, ["width", "height"]); 116 | } 117 | 118 | /// Alias to create a 2d size vector of size_t. 119 | public alias Size = Size2dVector!size_t; 120 | 121 | /// Alias to create a 2d size vector of float. 122 | public alias SizeF = Size2dVector!float; 123 | 124 | /// A 3d size vector. 125 | private class Size3dVector(T) { 126 | mixin Vector!(T, ["width", "height", "depth"]); 127 | } 128 | 129 | /// Alias to create a 3d size vector of size_t. 130 | public alias Size3d = Size3dVector!size_t; 131 | 132 | /// Alias to create a 3d size vector of float. 133 | public alias Size3dF = Size3dVector!float; 134 | 135 | /// An edge vector. 136 | private class EdgeVector(T) { 137 | mixin Vector!(T, ["top", "right", "bottom", "left"]); 138 | } 139 | 140 | /// Alias to create an edge vector of ptrdiff_t. 141 | public alias Edge = EdgeVector!ptrdiff_t; 142 | 143 | /// Alias to create an edge vector of float. 144 | public alias EdgeF = EdgeVector!float; 145 | -------------------------------------------------------------------------------- /src/debugging/log.d: -------------------------------------------------------------------------------- 1 | /** 2 | * Module for logging. (Not in use yet ...) 3 | * 4 | * Authors: 5 | * Jacob Jensen 6 | * License: 7 | * https://github.com/PoisonEngine/poison-ui/blob/master/LICENSE 8 | */ 9 | module poison.debugging.log; 10 | 11 | void log(string msg) { 12 | import std.stdio : writeln; 13 | writeln(msg); 14 | } 15 | -------------------------------------------------------------------------------- /src/debugging/package.d: -------------------------------------------------------------------------------- 1 | /** 2 | * Package module for debugging. 3 | * 4 | * Authors: 5 | * Jacob Jensen 6 | * License: 7 | * https://github.com/PoisonEngine/poison-ui/blob/master/LICENSE 8 | */ 9 | module poison.debugging; 10 | 11 | public { 12 | import poison.debugging.log; 13 | } 14 | -------------------------------------------------------------------------------- /src/main.d: -------------------------------------------------------------------------------- 1 | /** 2 | * Main module. 3 | * 4 | * Authors: 5 | * Jacob Jensen 6 | * License: 7 | * https://github.com/PoisonEngine/poison-ui/blob/master/LICENSE 8 | */ 9 | module main; 10 | 11 | /// The entry point. 12 | private void main(string[] args) { 13 | try { 14 | import poison.ui.fonts; 15 | loadFonts("resources\\poison\\fonts"); 16 | 17 | import poison.ui.styles; 18 | 19 | version (Poison_SharedStyles) { 20 | loadStyleSheet("resources\\poison\\styles\\poison.pss"); 21 | } 22 | 23 | version (Poison_Win_XP) { 24 | loadStyleSheet("resources\\poison\\styles\\win-xp.pss"); 25 | } 26 | else version (Poison_Win_Vista) { 27 | loadStyleSheet("resources\\poison\\styles\\win-vista.pss"); 28 | } 29 | else version (Poison_Win_7) { 30 | loadStyleSheet("resources\\poison\\styles\\win-7.pss"); 31 | } 32 | else version (Poison_Win_8) { 33 | loadStyleSheet("resources\\poison\\styles\\win-8.pss"); 34 | } 35 | else version (Poison_Win_10) { 36 | loadStyleSheet("resources\\poison\\styles\\win-10.pss"); 37 | } 38 | else version (Poison_Linux) { 39 | loadStyleSheet("resources\\poison\\styles\\linux.pss"); 40 | } 41 | else version (Poison_OSX) { 42 | loadStyleSheet("resources\\poison\\styles\\osx.pss"); 43 | } 44 | else version (Poison_Android) { 45 | loadStyleSheet("resources\\poison\\styles\\android.pss"); 46 | } 47 | else version (Poison_iOS) { 48 | loadStyleSheet("resources\\poison\\styles\\ios.pss"); 49 | } 50 | else version (Poison_WindowsPhone) { 51 | loadStyleSheet("resources\\poison\\styles\\poison-wp.pss"); 52 | } 53 | 54 | import application; 55 | run(); 56 | } 57 | catch (Exception e) { 58 | import std.stdio : writeln, readln; 59 | 60 | writeln(e); 61 | readln(); 62 | } 63 | catch (Throwable t) { 64 | // TODO: exit program here ... 65 | import std.stdio : writeln, readln; 66 | 67 | writeln("Fatal error ..."); 68 | writeln(t); 69 | readln(); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/ui/component.d: -------------------------------------------------------------------------------- 1 | /** 2 | * Module for a component. 3 | * 4 | * Authors: 5 | * Jacob Jensen 6 | * License: 7 | * https://github.com/PoisonEngine/poison-ui/blob/master/LICENSE 8 | */ 9 | module poison.ui.component; 10 | 11 | import std.algorithm : filter; 12 | import std.array : array; 13 | import std.concurrency : thisTid; 14 | 15 | import poison.ui.space; 16 | import poison.core : ActionArgs, Point, Size, EventArgs, ChangeEventArgs, executeUI, isUIThread, CrossThreadingExeption; 17 | import poison.ui.window; 18 | import poison.ui.graphics; 19 | import poison.ui.paint; 20 | import poison.ui.picture; 21 | import poison.ui.container; 22 | 23 | public import dsfml.graphics : RenderWindow; 24 | 25 | /// The next component id. 26 | private size_t nextId = 0; 27 | 28 | /// A component, which is the base for all controls and elements. 29 | class Component : Space { 30 | private: 31 | /// The name. 32 | string _name; 33 | 34 | /// The inner text. 35 | dstring _innerText; 36 | 37 | /// The outer text. 38 | dstring _outerText; 39 | 40 | /// Boolean determining whether it's disabled or not. 41 | bool _disabled; 42 | 43 | /// Boolean determining whether it's hidden or not. 44 | bool _hidden; 45 | 46 | /// The id. 47 | size_t _id; 48 | 49 | /// The layer. 50 | ptrdiff_t _layer; 51 | 52 | /// The parent window. 53 | Window _parentWindow; 54 | 55 | /// The parent container. 56 | Container _parentContainer; 57 | 58 | /// The graphics. 59 | Graphics _graphics; 60 | 61 | /// Style selectors. 62 | string[] _selectors; 63 | 64 | /// Selectors to render with. 65 | string[] _renderSelectors; 66 | 67 | /// The selector name. 68 | string _selectorName; 69 | 70 | protected: 71 | /** 72 | * Creates a new component. 73 | * Params: 74 | * name = The name of the component. 75 | * initialSize = The initial size of the component. 76 | */ 77 | this(string name, Size initialSize) { 78 | if (!isUIThread) { 79 | throw new CrossThreadingExeption(thisTid, "Cannot create a component outside the UI thread. Consider using exeuteUI();"); 80 | } 81 | 82 | super(new Point(0, 0), initialSize); 83 | 84 | _name = name; 85 | _layer = -1; 86 | _id = nextId++; 87 | _graphics = new Graphics(); 88 | _selectors = ["component"]; 89 | _selectorName = "#" ~ _name; 90 | } 91 | 92 | /** 93 | * Creates a new component. 94 | * Params: 95 | * name = The name of the component. 96 | */ 97 | this(string name) { 98 | this(name, new Size(100, 100)); 99 | } 100 | 101 | @property { 102 | /// Gets the graphics of the component. 103 | Graphics graphics() { return _graphics; } 104 | } 105 | 106 | /** 107 | * Draws the component. Override this! 108 | * Params: 109 | * window = The window to draw the component to. 110 | */ 111 | void draw(RenderWindow window) { 112 | if (_graphics.displayableBackgroundRect) { 113 | window.draw(_graphics.backgroundRect); 114 | } 115 | 116 | auto picture = _graphics.backgroundPicture; 117 | 118 | if (picture && _graphics.displayableBackgroundPicture) { 119 | if (picture.backgroundSprite) { 120 | window.draw(picture.backgroundSprite); 121 | } 122 | 123 | if (picture.drawingSprite) { 124 | window.draw(picture.drawingSprite); 125 | } 126 | } 127 | } 128 | 129 | /** 130 | * Processes the component during application cycles. 131 | * Params: 132 | * window = The render window to process. 133 | */ 134 | void process(RenderWindow window) { 135 | if (!_hidden) { 136 | draw(window); 137 | } 138 | } 139 | 140 | public: 141 | @property { 142 | /// Gets the name of the component. 143 | string name() { return _name; } 144 | 145 | /// Gets a boolean determining whether the component is enabled or not. 146 | bool enabled() { return !_disabled; } 147 | 148 | /// Sets a boolean determining whether the component is enabled or not. 149 | void enabled(bool isEnabled) { 150 | disabled = !isEnabled; 151 | } 152 | 153 | /// Gets a boolean determining whether the component is disabled or not. 154 | bool disabled() { return _disabled; } 155 | 156 | /// Sets a boolean determining whether the component is disabled. 157 | void disabled(bool isDisabled) { 158 | executeUI({ 159 | _disabled = isDisabled; 160 | 161 | fireEvent(_disabled ? "beforeDisabled" : "beforeEnabled", EventArgs.empty); 162 | 163 | updateSelectors(); 164 | render(); 165 | 166 | fireEvent(_disabled ? "disabled" : "enabled", EventArgs.empty); 167 | }); 168 | } 169 | 170 | /// Gets a boolean determining whether the component is visible or not. 171 | bool visible() { return !_hidden; } 172 | 173 | /// Sets a boolean determining whether the component is visible or not. 174 | void visible(bool isVisible) { 175 | hidden = !isVisible; 176 | } 177 | 178 | /// Gets a boolean determining whether the component is hidden or not. 179 | bool hidden() { return _hidden; } 180 | 181 | /// Sets a boolean determining whether the component is hidden or not. 182 | void hidden(bool isHidden) { 183 | executeUI({ 184 | _hidden = isHidden; 185 | 186 | fireEvent(_hidden ? "beforeHide" : "beforeShow", EventArgs.empty); 187 | 188 | if (!_hidden) { 189 | render(); 190 | } 191 | 192 | fireEvent(_hidden ? "hide" : "show", EventArgs.empty); 193 | }); 194 | } 195 | 196 | /// Gets the inner text of the component. 197 | dstring innerText() { return _innerText; } 198 | 199 | /// Sets the inner text of the component. 200 | void innerText(dstring newInnerText) { 201 | executeUI({ 202 | auto oldInnerText = _innerText; 203 | _innerText = newInnerText; 204 | 205 | fireEvent("innerText", new ChangeEventArgs!dstring(oldInnerText, _innerText)); 206 | 207 | render(); 208 | }); 209 | } 210 | 211 | /// Gets the outer text of the component. 212 | dstring outerText() { return _outerText; } 213 | 214 | /// Sets the outer text of the component. 215 | void outerText(dstring newOuterText) { 216 | executeUI({ 217 | auto oldOuterText = _outerText; 218 | _outerText = newOuterText; 219 | 220 | fireEvent("outerText", new ChangeEventArgs!dstring(oldOuterText, _outerText)); 221 | 222 | render(); 223 | }); 224 | } 225 | 226 | /// Ges the position of the component. 227 | override Point position() { return super.position; } 228 | 229 | /// Sets the position of the component. 230 | override void position(Point newPoint) { 231 | executeUI({ 232 | super.position = newPoint; 233 | 234 | render(); 235 | }); 236 | } 237 | 238 | /// Gets the size of the component. 239 | override Size size() { return super.size; } 240 | 241 | /// Sets the size of the component. 242 | override void size(Size newSize) { 243 | executeUI({ 244 | super.size = newSize; 245 | 246 | render(); 247 | }); 248 | } 249 | 250 | /// Gets the layer of the component. 251 | ptrdiff_t layer() { return _layer; } 252 | 253 | /// Gets the parent window of the component. 254 | Window parentWindow() { 255 | return _parentWindow; 256 | } 257 | 258 | /// Gets the parent container of the component. 259 | Container parentContainer() { 260 | return _parentContainer; 261 | } 262 | } 263 | 264 | /// Renders the component. Override this! 265 | void render() { 266 | _graphics.size = super.size; 267 | _graphics.position = super.position; 268 | 269 | renderSub(); 270 | } 271 | 272 | /// Shows the component. 273 | void show() { 274 | visible = true; 275 | } 276 | 277 | /// Hides the component. 278 | void hide() { 279 | hidden = true; 280 | } 281 | 282 | /// Enables the component. 283 | void enable() { 284 | enabled = true; 285 | } 286 | 287 | /// Disables the component. 288 | void disable() { 289 | disabled = true; 290 | } 291 | 292 | /** 293 | * Adds a style selector. 294 | * Params: 295 | * selector = The selector to add. 296 | */ 297 | void addSelector(string selector) { 298 | _selectors ~= selector; 299 | 300 | updateSelectors(); 301 | } 302 | 303 | /** 304 | * Removes a style selector. 305 | * Params: 306 | * The style selector to remove. 307 | */ 308 | void removeSelector(string selector) { 309 | _selectors = _selectors.filter!((s) { return s != selector; }).array; 310 | 311 | updateSelectors(); 312 | } 313 | 314 | /** 315 | * Checks whether the component intersects with a point. 316 | * Params: 317 | * p = The point to check for intersection with. 318 | * Returns: 319 | * True if the component intersects with a point. 320 | */ 321 | override bool intersect(Point p) { 322 | auto pIntersects = _parentContainer ? _parentContainer.intersect(p) : true; 323 | 324 | return pIntersects && super.intersect(p); 325 | } 326 | 327 | /** 328 | * Checks whether the component intersects with another space. 329 | * Params: 330 | * target = The space to check for intersection with. 331 | * Returns: 332 | * True if the component intersects with a space. 333 | */ 334 | override bool intersect(Space target) { 335 | auto pIntersects = _parentContainer ? _parentContainer.intersect(target) : true; 336 | 337 | return pIntersects && super.intersect(target); 338 | } 339 | 340 | private: 341 | /// Updates the selectors. 342 | void updateSelectors() { 343 | auto prefix = (_disabled ? ":disabled" : ":enabled"); 344 | 345 | _renderSelectors ~= _selectors; 346 | 347 | foreach (selector; _selectors) { 348 | _renderSelectors ~= selector ~ prefix; 349 | } 350 | 351 | _renderSelectors ~= _selectorName; 352 | _renderSelectors ~= _selectorName ~ prefix; 353 | 354 | updateStyles(); 355 | } 356 | 357 | package(poison): 358 | /// Renders the sub rectangles for the graphics of the component. 359 | void renderSub() { 360 | if (_graphics && _parentContainer) { 361 | _graphics.renderSub(_parentContainer.position, _parentContainer.size); 362 | _hidden = !super.intersect(_parentContainer); // We set it directly to avoid events ... 363 | } 364 | } 365 | 366 | /** 367 | * Processes the component during application cycles. 368 | * Params: 369 | * window = The render window to process. 370 | */ 371 | void processInternal(RenderWindow window) { 372 | process(window); 373 | } 374 | 375 | /// Updates the styles of the component. 376 | void updateStyles() { 377 | import poison.ui.styles; 378 | 379 | if (_renderSelectors) { 380 | Size newSize; 381 | Point newPosition; 382 | 383 | foreach (selector; _renderSelectors) { 384 | auto styleEntry = getStyleEntry(selector); 385 | 386 | if (styleEntry) { 387 | if (styleEntry.backgroundPicture) { 388 | _graphics.backgroundPicture = new Picture(styleEntry.backgroundPicture); 389 | _graphics.position = super.position; 390 | _graphics.backgroundPicture.finalize(); 391 | } 392 | 393 | _graphics.backgroundPaint = styleEntry.backgroundPaint; 394 | _graphics.foregroundPaint = styleEntry.foregroundPaint; 395 | 396 | _graphics.font = styleEntry.font; 397 | _graphics.fontSize = styleEntry.fontSize; 398 | 399 | if (styleEntry.hasSize) { 400 | newSize = styleEntry.size; 401 | } 402 | 403 | if (styleEntry.hasPosition) { 404 | newPosition = styleEntry.position; 405 | } 406 | } 407 | } 408 | 409 | if (newSize && !_graphics.hasSize) { 410 | this.size = newSize; 411 | _graphics.hasSize = true; 412 | } 413 | 414 | if (newPosition && !_graphics.hasPosition) { 415 | this.position = newPosition; 416 | _graphics.hasPosition = true; 417 | } 418 | } 419 | 420 | render(); 421 | } 422 | 423 | @property { 424 | /// Gets the id of the component. 425 | size_t id() { return _id; } 426 | 427 | /// Sets the layer of the component. 428 | void layer(ptrdiff_t newLayer) { 429 | _layer = newLayer; 430 | } 431 | 432 | /// Sets the parent window of the component. 433 | void parentWindow(Window newParentWindow) { 434 | _parentWindow = newParentWindow; 435 | } 436 | 437 | /// Sets the parent container of the component. 438 | void parentContainer(Container newParentContainer) { 439 | _parentContainer = newParentContainer; 440 | } 441 | } 442 | } 443 | -------------------------------------------------------------------------------- /src/ui/container.d: -------------------------------------------------------------------------------- 1 | /** 2 | * Module for a container. 3 | * 4 | * Authors: 5 | * Jacob Jensen 6 | * License: 7 | * https://github.com/PoisonEngine/poison-ui/blob/master/LICENSE 8 | */ 9 | module poison.ui.container; 10 | 11 | import std.algorithm : filter; 12 | import std.array : array; 13 | 14 | public import dsfml.graphics : RenderWindow; 15 | 16 | import poison.ui.component; 17 | import poison.core : Size, Point, ActionArgs, executeUI; 18 | 19 | /// A container component. 20 | class Container : Component { 21 | private: 22 | /// The components. 23 | Component[][] _components; 24 | 25 | public: 26 | /** 27 | * Creates a new container. 28 | * Params: 29 | * name = The name of the container. 30 | * initialSize = The initial size of the container. 31 | * layers = The layers of the container. 32 | */ 33 | this(string name, Size initialSize, size_t layers) { 34 | assert(layers > 0); 35 | 36 | super(name, initialSize); 37 | 38 | _components = new Component[][layers]; 39 | 40 | foreach (i; 0 .. _components.length) { 41 | _components[i] = []; 42 | } 43 | 44 | addSelector("container"); 45 | } 46 | 47 | /** 48 | * Creates a new container. 49 | * Params: 50 | * name = The name of the container. 51 | * layers = The layers of the container. 52 | */ 53 | this(string name, size_t layers) { 54 | this(name, new Size(100, 100), layers); 55 | } 56 | 57 | @property { 58 | /// Gets a boolean determining whether the container is disabled or not. 59 | override bool disabled() { return super.disabled; } 60 | 61 | /// Sets a boolean determining whether the container is disabled or not. 62 | override void disabled(bool isDisabled) { 63 | super.disabled = isDisabled; 64 | 65 | foreach (children; _components) { 66 | if (children) { 67 | foreach (component; children) { 68 | if (component) { 69 | component.disabled = isDisabled; 70 | } 71 | } 72 | } 73 | } 74 | } 75 | } 76 | 77 | /** 78 | * Adds a component to the container. 79 | * Params: 80 | * child = The child component to add. 81 | * layer = The layer to add the component to. 82 | */ 83 | void add(Component child, size_t layer) { 84 | assert(layer >= 0); 85 | assert(layer < _components.length); 86 | assert(child !is null); 87 | assert(child.layer == -1); 88 | 89 | executeUI({ 90 | _components[layer] ~= child; 91 | 92 | child.layer = cast(ptrdiff_t)layer; 93 | child.parentWindow = parentWindow; 94 | child.parentContainer = this; 95 | parentWindow._windowComponents[child.id] = child; 96 | 97 | child.disabled = child.disabled; // HACK: calls updateStyles() internally. 98 | child.render(); 99 | child.show(); 100 | }); 101 | } 102 | 103 | /** 104 | * Adds an array of components to the container. 105 | * Params: 106 | * components = The child components to add. 107 | * layer = The layer to add the components to. 108 | */ 109 | void add(Component[] components, size_t layer) { 110 | assert(components && components.length); 111 | 112 | executeUI({ 113 | foreach (component; components) { 114 | add(component, layer); 115 | } 116 | }); 117 | } 118 | 119 | /** 120 | * Removes a component from the container. 121 | * Params: 122 | * child = The child component to remove. 123 | */ 124 | void remove(Component child) { 125 | assert(child.layer != -1); 126 | 127 | executeUI({ 128 | auto children = _components[child.layer]; 129 | 130 | if (children) { 131 | children = children.filter!((c) { return c.id == child.id; }).array; 132 | 133 | _components[layer] = children; 134 | } 135 | 136 | child.layer = -1; 137 | child.parentWindow._windowComponents.remove(child.id); 138 | child.parentWindow = null; 139 | child.parentContainer = null; 140 | }); 141 | } 142 | 143 | /** 144 | * Removes a component from the container. 145 | * Params: 146 | * name = The name of the component. 147 | */ 148 | void remove(string name) { 149 | executeUI({ 150 | foreach (ref children; _components) { 151 | if (children) { 152 | children = children.filter!((c) { return c.name == name; }).array; 153 | } 154 | } 155 | }); 156 | } 157 | 158 | /// Clears the component for children. 159 | void clear() { 160 | executeUI({ 161 | foreach (children; _components) { 162 | if (children) { 163 | foreach (component; children) { 164 | if (component) { 165 | component.layer = -1; 166 | } 167 | } 168 | } 169 | } 170 | 171 | _components = new Component[][_components.length]; 172 | }); 173 | } 174 | 175 | /** 176 | * Clears the components for a specific layer. 177 | * Params: 178 | * layer = The layer to clear. 179 | */ 180 | void clear(size_t layer) { 181 | assert(layer >= 0); 182 | assert(layer < _components.length); 183 | 184 | executeUI({ 185 | auto children = _components[layer]; 186 | 187 | if (children) { 188 | foreach (component; children) { 189 | if (component) { 190 | component.layer = -1; 191 | } 192 | } 193 | } 194 | 195 | _components[layer] = []; 196 | }); 197 | } 198 | 199 | protected: 200 | /** 201 | * Processes the container during application cycles. 202 | * Params: 203 | * window = The render window to process. 204 | */ 205 | override void process(RenderWindow window) { 206 | super.process(window); 207 | 208 | foreach (children; _components) { 209 | if (children) { 210 | foreach (component; children) { 211 | if (component) { 212 | component.processInternal(window); 213 | } 214 | } 215 | } 216 | } 217 | } 218 | } 219 | -------------------------------------------------------------------------------- /src/ui/controls/package.d: -------------------------------------------------------------------------------- 1 | /** 2 | * Package module for controls. 3 | * 4 | * Authors: 5 | * Jacob Jensen 6 | * License: 7 | * https://github.com/PoisonEngine/poison-ui/blob/master/LICENSE 8 | */ 9 | module poison.ui.controls; 10 | 11 | public { 12 | import poison.ui.controls.picturebox; 13 | } 14 | -------------------------------------------------------------------------------- /src/ui/controls/picturebox.d: -------------------------------------------------------------------------------- 1 | /** 2 | * Module for a picturebox. 3 | * 4 | * Authors: 5 | * Jacob Jensen 6 | * License: 7 | * https://github.com/PoisonEngine/poison-ui/blob/master/LICENSE 8 | */ 9 | module poison.ui.controls.picturebox; 10 | 11 | import poison.ui.picture; 12 | import poison.ui.sprite; 13 | import poison.ui.container; 14 | 15 | import poison.core : Point, Size; 16 | 17 | /// A container for a picture. 18 | class PictureBox : Container { 19 | public: 20 | /** 21 | * Creates a new picture box. 22 | * Params: 23 | * name = The name of the pictue box. 24 | * imageFile = The image file to load into the picture. 25 | * layers = The amount of layers the picture box has. 26 | */ 27 | this(string name, string imageFile, size_t layers = 1) { 28 | auto picture = new Picture(imageFile); 29 | picture.finalize(); 30 | 31 | this(name, picture, layers); 32 | } 33 | 34 | /** 35 | * Creates a new picture box. 36 | * Params: 37 | * name = The name of the pictue box. 38 | * picture = The picture to initialize with. 39 | * layers = The amount of layers the picture box has. 40 | */ 41 | this(string name, Picture picture, size_t layers) { 42 | assert(picture !is null); 43 | super(name, layers); 44 | 45 | super.graphics.backgroundPicture = picture; 46 | size = super.graphics.backgroundPicture.size; 47 | 48 | addSelector("picturebox"); 49 | } 50 | 51 | @property { 52 | /// Gets the picture. 53 | Picture picture() { return super.graphics.backgroundPicture; } 54 | 55 | /// Gets the position of the picturebox. 56 | override Point position() { return super.position; } 57 | 58 | /// Sets the position of the picturebox. 59 | override void position(Point newPosition) { 60 | super.graphics.backgroundPicture.position = newPosition; 61 | 62 | super.position = newPosition; 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/ui/fonts.d: -------------------------------------------------------------------------------- 1 | /** 2 | * Module for fonts. 3 | * 4 | * Authors: 5 | * Jacob Jensen 6 | * License: 7 | * https://github.com/PoisonEngine/poison-ui/blob/master/LICENSE 8 | */ 9 | module poison.ui.fonts; 10 | 11 | import std.file : dirEntries, SpanMode; 12 | import std.algorithm : filter, endsWith; 13 | import std.string : toLower; 14 | import std.path : stripExtension, baseName; 15 | 16 | public import dsfml.graphics : Font; 17 | 18 | /// The fonts. 19 | private Font[string] _fonts; 20 | 21 | /// Enumeration of font styles. 22 | enum FontStyle { 23 | /// Normal font style. 24 | normal = "", 25 | 26 | /// Bold font style. 27 | bold = "b", 28 | 29 | /// Italic font style. 30 | italic = "i", 31 | 32 | /// Bold & italic font style. 33 | boldItalic = "z" 34 | } 35 | 36 | /** 37 | * Loads all fonts within a specific path. 38 | * Params: 39 | * path = The path of the fonts. 40 | */ 41 | void loadFonts(string path) { 42 | auto entries = dirEntries(path, SpanMode.depth).filter!(f => f.name.toLower().endsWith(".ttf")); 43 | 44 | foreach (string filePath; entries) { 45 | loadFont(filePath); 46 | } 47 | } 48 | 49 | /** 50 | * Loads a font by its path or retrieves it from the font cache. 51 | * Params: 52 | * path = The path of the font. 53 | * Note: 54 | * If the path is specified as a font-name, it must be prefixed with its proper font-style suffix. Use retrieveFont for easier access. 55 | * Returns: 56 | * The font loaded from its path or name. 57 | */ 58 | Font loadFont(string path) { 59 | auto font = _fonts.get(path, null); 60 | 61 | if (font) { 62 | return font; 63 | } 64 | 65 | font = new Font(); 66 | font.loadFromFile(path); 67 | 68 | auto name = stripExtension(baseName(path)); 69 | 70 | _fonts[name] = font; 71 | _fonts[path] = font; 72 | 73 | return font; 74 | } 75 | 76 | /** 77 | * Retrieves a font by a name and style. 78 | * Params: 79 | * fontName = The name of the font to retrieve. 80 | * style = The style of the font. 81 | * Returns: 82 | * The font retrieved by its name and style. 83 | */ 84 | Font retrieveFont(string fontName, FontStyle style) { 85 | return _fonts.get(fontName ~ style, null); 86 | } 87 | -------------------------------------------------------------------------------- /src/ui/graphics.d: -------------------------------------------------------------------------------- 1 | /** 2 | * Module for graphics. 3 | * 4 | * Authors: 5 | * Jacob Jensen 6 | * License: 7 | * https://github.com/PoisonEngine/poison-ui/blob/master/LICENSE 8 | */ 9 | module poison.ui.graphics; 10 | 11 | import dsfml.graphics : RectangleShape; 12 | import dsfml.system : Vector2f; 13 | 14 | import poison.ui.paint; 15 | import poison.core : Size, Point; 16 | import poison.ui.picture; 17 | import poison.ui.fonts; 18 | 19 | /// Original space struct for graphical elements. 20 | private struct OriginalSpace(TPosition,TSize) { 21 | /// The original x position. 22 | TPosition x; 23 | 24 | /// The original y position. 25 | TPosition y; 26 | 27 | /// The original width. 28 | TSize width; 29 | 30 | /// The original height. 31 | TSize height; 32 | } 33 | 34 | /// A sub rect of overflow calculation results. 35 | private struct SubRect { 36 | /// The x coordinate. 37 | float x; 38 | 39 | /// The y coordinate. 40 | float y; 41 | 42 | /// The width. 43 | float width; 44 | 45 | /// The height. 46 | float height; 47 | 48 | /// The overflow top. 49 | float overflowTop; 50 | 51 | /// The overflow right. 52 | float overflowRight; 53 | 54 | /// The overflow bottom. 55 | float overflowBottom; 56 | 57 | /// The overflow left. 58 | float overflowLeft; 59 | } 60 | 61 | /// Graphics wrapper for components. 62 | class Graphics { 63 | private: 64 | /// The background paint. 65 | Paint _backgroundPaint; 66 | 67 | /// The foreground paint. 68 | Paint _foregroundPaint; 69 | 70 | /// The low-level background rectangle. 71 | RectangleShape _backgroundRect; 72 | 73 | /// The original background rectangle's space. 74 | OriginalSpace!(float,float) _originalBackgroundRect; 75 | 76 | /// The background picture. 77 | Picture _backgroundPicture; 78 | 79 | /// The original background ´picture's space. 80 | OriginalSpace!(ptrdiff_t,int) _originalBackgroundPicture; 81 | 82 | /// The size. 83 | Size _size; 84 | 85 | /// The position. 86 | Point _position; 87 | 88 | /// The font. 89 | Font _font; 90 | 91 | /// The font size. 92 | uint _fontSize; 93 | 94 | /// Boolean determining whether the background rectangle is displayable or not. 95 | bool _displayableBackgroundRect; 96 | 97 | /// Boolean determining whether the background picture is displayable or not. 98 | bool _displayableBackgroundPicture; 99 | 100 | /// Boolean determining whether the graphics has a size. 101 | bool _hasSize; 102 | 103 | /// Boolean determining whether the graphics has a position. 104 | bool _hasPosition; 105 | 106 | public: 107 | final: 108 | /// Creates a new graphics wrapper. 109 | this() { 110 | _backgroundPaint = transparent; 111 | _foregroundPaint = transparent; 112 | 113 | _size = new Size(0,0); 114 | _position = new Point(0,0); 115 | 116 | _fontSize = 13; 117 | _displayableBackgroundRect = true; 118 | _displayableBackgroundPicture = true; 119 | _originalBackgroundRect = OriginalSpace!(float,float)(0,0,0,0); 120 | _originalBackgroundPicture = OriginalSpace!(ptrdiff_t, int)(0,0,0,0); 121 | } 122 | 123 | @property { 124 | /// Gets the background paint. 125 | Paint backgroundPaint() { return _backgroundPaint; } 126 | 127 | /// Sets the background paint. 128 | void backgroundPaint(Paint newPaint) { 129 | _backgroundPaint = newPaint; 130 | 131 | if (_backgroundRect) { 132 | _backgroundRect.fillColor = _backgroundPaint.sfmlColor; 133 | } 134 | } 135 | 136 | /// Gets the foreground paint. 137 | Paint foregroundPaint() { return _foregroundPaint; } 138 | 139 | /// Sets the foreground paint. 140 | void foregroundPaint(Paint newPaint) { 141 | _foregroundPaint = newPaint; 142 | } 143 | 144 | /// Gets the low-level background rectangle. 145 | RectangleShape backgroundRect() { return _backgroundRect; } 146 | 147 | /// Gets the background picture. 148 | Picture backgroundPicture() { return _backgroundPicture; } 149 | 150 | /// Sets the background picture. 151 | void backgroundPicture(Picture newBackgroundPicture) { 152 | _backgroundPicture = newBackgroundPicture; 153 | 154 | if (_backgroundPicture) { 155 | _backgroundPicture.position = _position; 156 | 157 | _originalBackgroundPicture.x = _position.x; 158 | _originalBackgroundPicture.y = _position.y; 159 | 160 | _originalBackgroundPicture.width = _size.width; 161 | _originalBackgroundPicture.height = _size.height; 162 | } 163 | } 164 | 165 | /// Gets the font. 166 | Font font() { return _font; } 167 | 168 | /// Sets the font. 169 | void font(Font newFont) { 170 | _font = newFont; 171 | } 172 | 173 | /// Gets the font size. 174 | uint fontSize() { return _fontSize; } 175 | 176 | /// Sets the font size. 177 | void fontSize(uint newFontSize) { 178 | _fontSize = newFontSize; 179 | } 180 | } 181 | 182 | package(poison): 183 | @property { 184 | /// Sets the position of the graphics. 185 | void position(Point newPosition) { 186 | _position = newPosition; 187 | 188 | if (_backgroundRect) { 189 | _backgroundRect.position = Vector2f(cast(float)_position.x, cast(float)_position.y); 190 | _originalBackgroundRect.x = _backgroundRect.position.x; 191 | _originalBackgroundRect.y = _backgroundRect.position.y; 192 | } 193 | 194 | if (_backgroundPicture) { 195 | _backgroundPicture.position = _position; 196 | _originalBackgroundPicture.x = cast(int)_position.x; 197 | _originalBackgroundPicture.y = cast(int)_position.y; 198 | } 199 | } 200 | 201 | /// Sets the size of the graphics. 202 | void size(Size newSize) { 203 | _size = newSize; 204 | 205 | _backgroundRect = new RectangleShape(Vector2f(cast(float)_size.width, cast(float)_size.height)); 206 | backgroundPaint = _backgroundPaint; 207 | position = _position; 208 | 209 | _originalBackgroundRect.width = _backgroundRect.size.x; 210 | _originalBackgroundRect.height = _backgroundRect.size.y; 211 | 212 | _originalBackgroundPicture.width = cast(int)_size.width; 213 | _originalBackgroundPicture.height = cast(int)_size.height; 214 | } 215 | } 216 | 217 | package(poison): 218 | @property { 219 | /// Gets a boolean determining whether the background rectangle is displayable. 220 | bool displayableBackgroundRect() { return _displayableBackgroundRect; } 221 | 222 | /// Gets a boolean determining whether the background picture is displayable. 223 | bool displayableBackgroundPicture() { return _displayableBackgroundPicture; } 224 | 225 | /// Gets the size of the graphics. Use hasSize before using this. 226 | Size size() { return _size; } 227 | 228 | /// Gets the position of the graphics. Use hasPosition before using this. 229 | Point position() { return _position; } 230 | 231 | /// Gets a boolean determining whether the graphics has a size or not. 232 | bool hasSize() { return _hasSize; } 233 | 234 | /// Sets a boolean determining whether the graphics has a size or not. 235 | void hasSize(bool setHasSize) { 236 | _hasSize = setHasSize; 237 | } 238 | 239 | /// Gets a boolean determining whether the graphics has a position or not. 240 | bool hasPosition() { return _hasPosition; } 241 | 242 | /// Sets a boolean determining whether the graphics has a position or not. 243 | void hasPosition(bool setHasPosition) { 244 | _hasPosition = setHasPosition; 245 | } 246 | } 247 | 248 | /** 249 | * Finalizes paint inputs for the graphics. 250 | * Params: 251 | * position = The position of the paint input. 252 | * size = The size of the paint input. 253 | * paint = The paint. 254 | */ 255 | void finalizePaint(Point position, Size size, Paint paint) { 256 | if (!_backgroundPicture) { 257 | _backgroundPicture = new Picture(size, transparent); 258 | } 259 | 260 | _backgroundPicture.draw(position, size, paint); 261 | } 262 | 263 | /** 264 | * Finalizes vertical gradient inputs for the graphics. 265 | * Params: 266 | * position = The position of the graident. 267 | * size = The size of the gradient. 268 | * fromPaint = The paint to draw the gradient from. 269 | * toPaint = The paint to draw the gradient to. 270 | */ 271 | void finalizeGradientVertical(Point position, Size size, Paint fromPaint, Paint toPaint) { 272 | if (!_backgroundPicture) { 273 | _backgroundPicture = new Picture(size, transparent); 274 | } 275 | 276 | _backgroundPicture.gradientVertical(position, size, fromPaint, toPaint); 277 | } 278 | 279 | /** 280 | * Finalizes horizontal gradient inputs for the graphics. 281 | * Params: 282 | * position = The position of the graident. 283 | * size = The size of the gradient. 284 | * fromPaint = The paint to draw the gradient from. 285 | * toPaint = The paint to draw the gradient to. 286 | */ 287 | void finalizeGradientHorizontal(Point position, Size size, Paint fromPaint, Paint toPaint) { 288 | if (!_backgroundPicture) { 289 | _backgroundPicture = new Picture(size, transparent); 290 | } 291 | 292 | _backgroundPicture.gradientHorizontal(position, size, fromPaint, toPaint); 293 | } 294 | 295 | /** 296 | * Renders the sub rectangles for the graphics elements. 297 | * Params: 298 | * position = The position to be relative to. 299 | * size = The size to be relative to. 300 | */ 301 | void renderSub(Point position, Size size) { 302 | import dsfml.graphics : IntRect; 303 | import dsfml.system : Vector2f; 304 | 305 | SubRect calculateOverflow(float targetX, float targetY, float targetWidth, float targetHeight) { 306 | import std.math : fmax; 307 | 308 | float overflowTop = fmax(0, (cast(float)position.y - targetY)); 309 | float overflowRight = fmax(0, (targetX + targetWidth) - (cast(float)position.x + cast(float)size.width)); 310 | float overflowBottom = fmax(0, (targetY + targetHeight) - (cast(float)position.y + cast(float)size.height)); 311 | float overflowLeft = fmax(0, (cast(float)position.x - targetX)); 312 | 313 | targetY += overflowTop; 314 | targetHeight -= overflowTop; 315 | targetWidth -= overflowRight; 316 | targetHeight -= overflowBottom; 317 | targetX += overflowLeft; 318 | targetWidth -= overflowLeft; 319 | 320 | return SubRect(targetX, targetY, targetWidth, targetHeight, overflowTop, overflowRight, overflowBottom, overflowLeft); 321 | } 322 | 323 | bool intersect(float width, float height, float x, float y) { 324 | return(x < cast(float)position.x + cast(float)size.width) && 325 | (cast(float)position.x < (x + width)) && 326 | (y < cast(float)position.y + cast(float)size.height) && 327 | (cast(float)position.y < y + height); 328 | } 329 | 330 | if (_backgroundRect) { 331 | _backgroundRect.size = Vector2f(_originalBackgroundRect.width, _originalBackgroundRect.height); 332 | _backgroundRect.position = Vector2f(_originalBackgroundRect.x, _originalBackgroundRect.y); 333 | 334 | auto rectSize = _backgroundRect.size; 335 | auto rectPosition = _backgroundRect.position; 336 | 337 | _displayableBackgroundRect = intersect(rectSize.x, rectSize.y, rectPosition.x, rectPosition.y); 338 | 339 | if (_displayableBackgroundRect) { 340 | auto overflow = calculateOverflow(rectPosition.x, rectPosition.y, rectSize.x, rectSize.y); 341 | 342 | _backgroundRect.size = Vector2f(overflow.width, overflow.height); 343 | _backgroundRect.position = Vector2f(overflow.x, overflow.y); 344 | } 345 | } 346 | 347 | if (_backgroundPicture) { 348 | _displayableBackgroundPicture = intersect(cast(float)_originalBackgroundPicture.x, cast(float)_originalBackgroundPicture.y, cast(float)_originalBackgroundPicture.width, cast(float)_originalBackgroundPicture.height); 349 | 350 | if (_displayableBackgroundPicture) { 351 | auto backgroundSprite = _backgroundPicture.backgroundSprite; 352 | auto drawingSprite = _backgroundPicture.drawingSprite; 353 | 354 | if (backgroundSprite) { 355 | backgroundSprite.position = new Point(_originalBackgroundPicture.x, _originalBackgroundPicture.y); 356 | backgroundSprite.textureRect = IntRect(0, 0, cast(int)_originalBackgroundPicture.width, cast(int)_originalBackgroundPicture.height); 357 | } 358 | 359 | if (drawingSprite) { 360 | drawingSprite.position = new Point(_originalBackgroundPicture.x, _originalBackgroundPicture.y); 361 | drawingSprite.textureRect = IntRect(0, 0, cast(int)_originalBackgroundPicture.width, cast(int)_originalBackgroundPicture.height); 362 | } 363 | 364 | auto overflow = calculateOverflow(cast(float)_originalBackgroundPicture.x, cast(float)_originalBackgroundPicture.y, cast(float)_originalBackgroundPicture.width, cast(float)_originalBackgroundPicture.height); 365 | 366 | if (backgroundSprite) { 367 | backgroundSprite.textureRect = IntRect(cast(int)overflow.overflowLeft, cast(int)overflow.overflowTop, cast(int)overflow.width, cast(int)overflow.height); 368 | backgroundSprite.position = new Point(cast(ptrdiff_t)overflow.x, cast(ptrdiff_t)overflow.y); 369 | } 370 | 371 | if (drawingSprite) { 372 | drawingSprite.textureRect = IntRect(cast(int)overflow.overflowLeft, cast(int)overflow.overflowTop, cast(int)overflow.width, cast(int)overflow.height); 373 | drawingSprite.position = new Point(cast(ptrdiff_t)overflow.x, cast(ptrdiff_t)overflow.y); 374 | } 375 | } 376 | } 377 | } 378 | } 379 | -------------------------------------------------------------------------------- /src/ui/package.d: -------------------------------------------------------------------------------- 1 | /** 2 | * Package module for ui. 3 | * 4 | * Authors: 5 | * Jacob Jensen 6 | * License: 7 | * https://github.com/PoisonEngine/poison-ui/blob/master/LICENSE 8 | */ 9 | module poison.ui; 10 | 11 | public { 12 | import poison.ui.paint; 13 | import poison.ui.fonts; 14 | 15 | import poison.ui.sprite; 16 | import poison.ui.picture; 17 | 18 | import poison.ui.graphics; 19 | import poison.ui.styles; 20 | 21 | import poison.ui.space; 22 | import poison.ui.component; 23 | import poison.ui.container; 24 | import poison.ui.window; 25 | 26 | import poison.ui.controls; 27 | } 28 | -------------------------------------------------------------------------------- /src/ui/paint.d: -------------------------------------------------------------------------------- 1 | /** 2 | * Module for paint. 3 | * 4 | * Authors: 5 | * Jacob Jensen 6 | * License: 7 | * https://github.com/PoisonEngine/poison-ui/blob/master/LICENSE 8 | */ 9 | module poison.ui.paint; 10 | 11 | import dsfml.graphics : Color; 12 | 13 | /// Paint structure. 14 | struct Paint { 15 | public: 16 | /// The red channel. 17 | ubyte r; 18 | /// The green channel. 19 | ubyte g; 20 | /// The blue channel. 21 | ubyte b; 22 | /// The alpha channel. 23 | ubyte a; 24 | 25 | @property { 26 | /// Gets the low-level sfml color equivalent to the paint. 27 | Color sfmlColor() { 28 | return Color(r,g,b,a); 29 | } 30 | } 31 | } 32 | 33 | /** 34 | * Gets a painting from RGBA. 35 | * Params: 36 | * r = The red channel. 37 | * g = The green channel. 38 | * b = The blue channel. 39 | * a = The alpha channel. 40 | * Returns: 41 | * The paint. 42 | */ 43 | Paint paintFromRGBA(ubyte r, ubyte g, ubyte b, ubyte a = 0xff) { 44 | Paint p; 45 | p.r = r; 46 | p.g = g; 47 | p.b = b; 48 | p.a = a; 49 | return p; 50 | } 51 | 52 | /** 53 | * Gets paint from a color name. 54 | * Params: 55 | * name = The name of the color. 56 | * Returns: 57 | * The paint. 58 | */ 59 | Paint paintFromName(string name) { 60 | import std.string : toLower; 61 | 62 | switch (name.toLower()) { 63 | case "aliceblue": return aliceBlue; 64 | case "antiquewhite": return antiqueWhite; 65 | case "aqua": return aqua; 66 | case "aquamarine": return aquamarine; 67 | case "azure": return azure; 68 | case "beige": return beige; 69 | case "bisque": return bisque; 70 | case "black": return black; 71 | case "blanchedalmond": return blanchedAlmond; 72 | case "blue": return blue; 73 | case "blueviolet": return blueViolet; 74 | case "brown": return brown; 75 | case "burlywood": return burlyWood; 76 | case "cadetblue": return cadetBlue; 77 | case "chartreuse": return chartreuse; 78 | case "chocolate": return chocolate; 79 | case "coral": return coral; 80 | case "cornflowerblue": return cornflowerBlue; 81 | case "cornsilk": return cornsilk; 82 | case "crimson": return crimson; 83 | case "cyan": return cyan; 84 | case "darkblue": return darkBlue; 85 | case "darkcyan": return darkCyan; 86 | case "darkgoldenrod": return darkGoldenrod; 87 | case "darkgrey": return darkGrey; 88 | case "darkgray": return darkGray; 89 | case "darkgreen": return darkGreen; 90 | case "darkkhaki": return darkKhaki; 91 | case "darkmagenta": return darkMagenta; 92 | case "darkolivegreen": return darkOliveGreen; 93 | case "darkorange": return darkOrange; 94 | case "darkorchid": return darkOrchid; 95 | case "darkred": return darkRed; 96 | case "darksalmon": return darkSalmon; 97 | case "darkseagreen": return darkSeaGreen; 98 | case "darkslateblue": return darkSlateBlue; 99 | case "darkslategray": return darkSlateGray; 100 | case "darkslategrey": return darkSlateGrey; 101 | case "darkturquoise": return darkTurquoise; 102 | case "darkviolet": return darkViolet; 103 | case "deeppink": return deepPink; 104 | case "deepskyblue": return deepSkyBlue; 105 | case "dimgray": return dimGray; 106 | case "dimgrey": return dimGrey; 107 | case "dodgerblue": return dodgerBlue; 108 | case "firebrick": return fireBrick; 109 | case "floralwhite": return floralWhite; 110 | case "forestgreen": return forestGreen; 111 | case "fuchsia": return fuchsia; 112 | case "gainsboro": return gainsboro; 113 | case "ghostwhite": return ghostWhite; 114 | case "gold": return gold; 115 | case "goldenrod": return goldenrod; 116 | case "gray": return gray; 117 | case "grey": return grey; 118 | case "green": return green; 119 | case "greenyellow": return greenYellow; 120 | case "honeydew": return honeydew; 121 | case "hotpink": return hotPink; 122 | case "indianred": return indianRed; 123 | case "indigo": return indigo; 124 | case "ivory": return ivory; 125 | case "khaki": return khaki; 126 | case "lavender": return lavender; 127 | case "lavenderblush": return lavenderBlush; 128 | case "lawngreen": return lawnGreen; 129 | case "lemonchiffon": return lemonChiffon; 130 | case "lightblue": return lightBlue; 131 | case "lightcoral": return lightCoral; 132 | case "lightcyan": return lightCyan; 133 | case "lightgoldenrodyellow": return lightGoldenrodYellow; 134 | case "lightgreen": return lightGreen; 135 | case "lightgray": return lightGray; 136 | case "lightgrey": return lightGrey; 137 | case "lightpink": return lightPink; 138 | case "lightsalmon": return lightSalmon; 139 | case "lightseagreen": return lightSeaGreen; 140 | case "lightskyblue": return lightSkyBlue; 141 | case "lightslategray": return lightSlateGray; 142 | case "lightslategrey": return lightSlateGrey; 143 | case "lightsteelblue": return lightSteelBlue; 144 | case "lightyellow": return lightYellow; 145 | case "lime": return lime; 146 | case "limegreen": return limeGreen; 147 | case "linen": return linen; 148 | case "magenta": return magenta; 149 | case "maroon": return maroon; 150 | case "mediumaquamarine": return mediumAquamarine; 151 | case "mediumblue": return mediumBlue; 152 | case "mediumorchid": return mediumOrchid; 153 | case "mediumpurple": return mediumPurple; 154 | case "mediumseagreen": return mediumSeaGreen; 155 | case "mediumslateblue": return mediumSlateBlue; 156 | case "mediumspringgreen": return mediumSpringGreen; 157 | case "mediumturquoise": return mediumTurquoise; 158 | case "mediumvioletred": return mediumVioletRed; 159 | case "midnightblue": return midnightBlue; 160 | case "mintcream": return mintCream; 161 | case "mistyrose": return mistyRose; 162 | case "moccasin": return moccasin; 163 | case "navajowhite": return navajoWhite; 164 | case "navy": return navy; 165 | case "oldlace": return oldLace; 166 | case "olive": return olive; 167 | case "olivedrab": return oliveDrab; 168 | case "orange": return orange; 169 | case "orangered": return orangeRed; 170 | case "orchid": return orchid; 171 | case "palegoldenrod": return paleGoldenrod; 172 | case "palegreen": return paleGreen; 173 | case "paleturquoise": return paleTurquoise; 174 | case "palevioletred": return paleVioletRed; 175 | case "papayawhip": return papayaWhip; 176 | case "peachpuff": return peachPuff; 177 | case "peru": return peru; 178 | case "pink": return pink; 179 | case "plum": return plum; 180 | case "powderblue": return powderBlue; 181 | case "purple": return purple; 182 | case "red": return red; 183 | case "rosybrown": return rosyBrown; 184 | case "royalblue": return royalBlue; 185 | case "saddlebrown": return saddleBrown; 186 | case "salmon": return salmon; 187 | case "sandybrown": return sandyBrown; 188 | case "seagreen": return seaGreen; 189 | case "seashell": return seashell; 190 | case "sienna": return sienna; 191 | case "silver": return silver; 192 | case "skyblue": return skyBlue; 193 | case "slateblue": return slateBlue; 194 | case "slategray": return slateGray; 195 | case "slategrey": return slateGrey; 196 | case "snow": return snow; 197 | case "springgreen": return springGreen; 198 | case "steelblue": return steelBlue; 199 | case "tan": return tan; 200 | case "teal": return teal; 201 | case "thistle": return thistle; 202 | case "tomato": return tomato; 203 | case "turquoise": return turquoise; 204 | case "violet": return violet; 205 | case "wheat": return wheat; 206 | case "white": return white; 207 | case "whitesmoke": return whiteSmoke; 208 | case "yellow": return yellow; 209 | case "yellowgreen": return yellowGreen; 210 | default: return transparent; 211 | } 212 | } 213 | 214 | /* Contrast & Misc */ 215 | 216 | /// White paint. 217 | Paint white = paintFromRGBA(0xff, 0xff, 0xff); 218 | 219 | /// Black paint. 220 | Paint black = paintFromRGBA(0x00, 0x00, 0x00); 221 | 222 | /// Transparent paint. 223 | Paint transparent = paintFromRGBA(0x00, 0x00, 0x00, 0x00); 224 | 225 | /* Standard */ 226 | 227 | /// Red paint. 228 | Paint red = paintFromRGBA(0xff, 0x00, 0x00); 229 | 230 | /// Green paint. 231 | Paint green = paintFromRGBA(0x00, 0xff, 0x00); 232 | 233 | /// Blue paint. 234 | Paint blue = paintFromRGBA(0x00, 0x00, 0xff); 235 | 236 | /* System colors */ 237 | 238 | /// AliceBlue paint. 239 | Paint aliceBlue = paintFromRGBA(0xf0, 0xf8, 0xff); 240 | 241 | /// AntiqueWhite paint. 242 | Paint antiqueWhite = paintFromRGBA(0xfa, 0xeb, 0xd7); 243 | 244 | /// Aqua paint. 245 | Paint aqua = paintFromRGBA(0x00, 0xff, 0xff); 246 | 247 | /// Aquamarine paint. 248 | Paint aquamarine = paintFromRGBA(0x7f, 0xff, 0xd4); 249 | 250 | /// Azure paint. 251 | Paint azure = paintFromRGBA(0xf0, 0xff, 0xff); 252 | 253 | /// Beige paint. 254 | Paint beige = paintFromRGBA(0xf5, 0xf5, 0xdc); 255 | 256 | /// Bisque paint. 257 | Paint bisque = paintFromRGBA(0xff, 0xe4, 0xc4); 258 | 259 | /// BlanchedAlmond paint. 260 | Paint blanchedAlmond = paintFromRGBA(0xff, 0xeb, 0xcd); 261 | 262 | /// BlueViolet paint. 263 | Paint blueViolet = paintFromRGBA(0x8a, 0x2b, 0xe2); 264 | 265 | /// Brown paint. 266 | Paint brown = paintFromRGBA(0xa5, 0x2a, 0x2a); 267 | 268 | /// BurlyWood paint. 269 | Paint burlyWood = paintFromRGBA(0xde, 0xb8, 0x87); 270 | 271 | /// CadetBlue paint. 272 | Paint cadetBlue = paintFromRGBA(0x5f, 0x9e, 0xa0); 273 | 274 | /// Chartreuse paint. 275 | Paint chartreuse = paintFromRGBA(0x7f, 0xff, 0x00); 276 | 277 | /// Chocolate paint. 278 | Paint chocolate = paintFromRGBA(0xd2, 0x69, 0x1e); 279 | 280 | /// Coral paint. 281 | Paint coral = paintFromRGBA(0xff, 0x7f, 0x50); 282 | 283 | /// CornflowerBlue paint. 284 | Paint cornflowerBlue = paintFromRGBA(0x64, 0x95, 0xed); 285 | 286 | /// Cornsilk paint. 287 | Paint cornsilk = paintFromRGBA(0xff, 0xf8, 0xdc); 288 | 289 | /// Crimson paint. 290 | Paint crimson = paintFromRGBA(0xdc, 0x14, 0x3c); 291 | 292 | /// Cyan paint. 293 | Paint cyan = paintFromRGBA(0x00, 0xff, 0xff); 294 | 295 | /// DarkBlue paint. 296 | Paint darkBlue = paintFromRGBA(0x00, 0x00, 0x8b); 297 | 298 | /// DarkCyan paint. 299 | Paint darkCyan = paintFromRGBA(0x00, 0x8b, 0x8b); 300 | 301 | /// DarkGoldenrod paint. 302 | Paint darkGoldenrod = paintFromRGBA(0xb8, 0x86, 0x0b); 303 | 304 | /// DarkGrey paint. 305 | Paint darkGrey = paintFromRGBA(0xa9, 0xa9, 0xa9); 306 | 307 | /// DarkGray paint. 308 | Paint darkGray = paintFromRGBA(0xa9, 0xa9, 0xa9); 309 | 310 | /// DarkGreen paint. 311 | Paint darkGreen = paintFromRGBA(0x00, 0x64, 0x00); 312 | 313 | /// DarkKhaki paint. 314 | Paint darkKhaki = paintFromRGBA(0xbd, 0xb7, 0x6b); 315 | 316 | /// DarkMagenta paint. 317 | Paint darkMagenta = paintFromRGBA(0x8b, 0x00, 0x8b); 318 | 319 | /// DarkOliveGreen paint. 320 | Paint darkOliveGreen = paintFromRGBA(0x55, 0x6b, 0x2f); 321 | 322 | /// DarkOrange paint. 323 | Paint darkOrange = paintFromRGBA(0xff, 0x8c, 0x00); 324 | 325 | /// DarkOrchid paint. 326 | Paint darkOrchid = paintFromRGBA(0x99, 0x32, 0xcc); 327 | 328 | /// DarkRed paint. 329 | Paint darkRed = paintFromRGBA(0x8b, 0x00, 0x00); 330 | 331 | /// DarkSalmon paint. 332 | Paint darkSalmon = paintFromRGBA(0xe9, 0x96, 0x7a); 333 | 334 | /// DarkSeaGreen paint. 335 | Paint darkSeaGreen = paintFromRGBA(0x8f, 0xbc, 0x8f); 336 | 337 | /// DarkSlateBlue paint. 338 | Paint darkSlateBlue = paintFromRGBA(0x48, 0x3d, 0x8b); 339 | 340 | /// DarkSlateGray paint. 341 | Paint darkSlateGray = paintFromRGBA(0x2f, 0x4f, 0x4f); 342 | 343 | /// DarkSlateGrey paint. 344 | Paint darkSlateGrey = paintFromRGBA(0x2f, 0x4f, 0x4f); 345 | 346 | /// DarkTurquoise paint. 347 | Paint darkTurquoise = paintFromRGBA(0x00, 0xce, 0xd1); 348 | 349 | /// DarkViolet paint. 350 | Paint darkViolet = paintFromRGBA(0x94, 0x00, 0xd3); 351 | 352 | /// DeepPink paint. 353 | Paint deepPink = paintFromRGBA(0xff, 0x14, 0x93); 354 | 355 | /// DeepSkyBlue paint. 356 | Paint deepSkyBlue = paintFromRGBA(0x00, 0xbf, 0xff); 357 | 358 | /// DimGray paint. 359 | Paint dimGray = paintFromRGBA(0x69, 0x69, 0x69); 360 | 361 | /// DimGrey paint. 362 | Paint dimGrey = paintFromRGBA(0x69, 0x69, 0x69); 363 | 364 | /// DodgerBlue paint. 365 | Paint dodgerBlue = paintFromRGBA(0x1e, 0x90, 0xff); 366 | 367 | /// FireBrick paint. 368 | Paint fireBrick = paintFromRGBA(0xb2, 0x22, 0x22); 369 | 370 | /// FloralWhite paint. 371 | Paint floralWhite = paintFromRGBA(0xff, 0xfa, 0xf0); 372 | 373 | /// ForestGreen paint. 374 | Paint forestGreen = paintFromRGBA(0x22, 0x8b, 0x22); 375 | 376 | /// Fuchsia paint. 377 | Paint fuchsia = paintFromRGBA(0xff, 0x00, 0xff); 378 | 379 | /// Gainsboro paint. 380 | Paint gainsboro = paintFromRGBA(0xdc, 0xdc, 0xdc); 381 | 382 | /// GhostWhite paint. 383 | Paint ghostWhite = paintFromRGBA(0xf8, 0xf8, 0xff); 384 | 385 | /// Gold paint. 386 | Paint gold = paintFromRGBA(0xff, 0xd7, 0x00); 387 | 388 | /// Goldenrod paint. 389 | Paint goldenrod = paintFromRGBA(0xda, 0xa5, 0x20); 390 | 391 | /// Gray paint. 392 | Paint gray = paintFromRGBA(0x80, 0x80, 0x80); 393 | 394 | /// Grey paint. 395 | Paint grey = paintFromRGBA(0x80, 0x80, 0x80); 396 | 397 | /// GreenYellow paint. 398 | Paint greenYellow = paintFromRGBA(0xad, 0xff, 0x2f); 399 | 400 | /// Honeydew paint. 401 | Paint honeydew = paintFromRGBA(0xf0, 0xff, 0xf0); 402 | 403 | /// HotPink paint. 404 | Paint hotPink = paintFromRGBA(0xff, 0x69, 0xb4); 405 | 406 | /// IndianRed paint. 407 | Paint indianRed = paintFromRGBA(0xcd, 0x5c, 0x5c); 408 | 409 | /// Indigo paint. 410 | Paint indigo = paintFromRGBA(0x4b, 0x00, 0x82); 411 | 412 | /// Ivory paint. 413 | Paint ivory = paintFromRGBA(0xff, 0xff, 0xf0); 414 | 415 | /// Khaki paint. 416 | Paint khaki = paintFromRGBA(0xf0, 0xe6, 0x8c); 417 | 418 | /// Lavender paint. 419 | Paint lavender = paintFromRGBA(0xe6, 0xe6, 0xfa); 420 | 421 | /// LavenderBlush paint. 422 | Paint lavenderBlush = paintFromRGBA(0xff, 0xf0, 0xf5); 423 | 424 | /// LawnGreen paint. 425 | Paint lawnGreen = paintFromRGBA(0x7c, 0xfc, 0x00); 426 | 427 | /// LemonChiffon paint. 428 | Paint lemonChiffon = paintFromRGBA(0xff, 0xfa, 0xcd); 429 | 430 | /// LightBlue paint. 431 | Paint lightBlue = paintFromRGBA(0xad, 0xd8, 0xe6); 432 | 433 | /// LightCoral paint. 434 | Paint lightCoral = paintFromRGBA(0xf0, 0x80, 0x80); 435 | 436 | /// LightCyan paint. 437 | Paint lightCyan = paintFromRGBA(0xe0, 0xff, 0xff); 438 | 439 | /// LightGoldenrodYellow paint. 440 | Paint lightGoldenrodYellow = paintFromRGBA(0xfa, 0xfa, 0xd2); 441 | 442 | /// LightGreen paint. 443 | Paint lightGreen = paintFromRGBA(0x90, 0xee, 0x90); 444 | 445 | /// LightGray paint. 446 | Paint lightGray = paintFromRGBA(0xd3, 0xd3, 0xd3); 447 | 448 | /// LightGrey paint. 449 | Paint lightGrey = paintFromRGBA(0xd3, 0xd3, 0xd3); 450 | 451 | /// LightPink paint. 452 | Paint lightPink = paintFromRGBA(0xff, 0xb6, 0xc1); 453 | 454 | /// LightSalmon paint. 455 | Paint lightSalmon = paintFromRGBA(0xff, 0xa0, 0x7a); 456 | 457 | /// LightSeaGreen paint. 458 | Paint lightSeaGreen = paintFromRGBA(0x20, 0xb2, 0xaa); 459 | 460 | /// LightSkyBlue paint. 461 | Paint lightSkyBlue = paintFromRGBA(0x87, 0xce, 0xfa); 462 | 463 | /// LightSlateGray paint. 464 | Paint lightSlateGray = paintFromRGBA(0x77, 0x88, 0x99); 465 | 466 | /// LightSlateGrey paint. 467 | Paint lightSlateGrey = paintFromRGBA(0x77, 0x88, 0x99); 468 | 469 | /// LightSteelBlue paint. 470 | Paint lightSteelBlue = paintFromRGBA(0xb0, 0xc4, 0xde); 471 | 472 | /// LightYellow paint. 473 | Paint lightYellow = paintFromRGBA(0xff, 0xff, 0xe0); 474 | 475 | /// Lime paint. 476 | Paint lime = paintFromRGBA(0x00, 0xff, 0x00); 477 | 478 | /// LimeGreen paint. 479 | Paint limeGreen = paintFromRGBA(0x32, 0xcd, 0x32); 480 | 481 | /// Linen paint. 482 | Paint linen = paintFromRGBA(0xfa, 0xf0, 0xe6); 483 | 484 | /// Magenta paint. 485 | Paint magenta = paintFromRGBA(0xff, 0x00, 0xff); 486 | 487 | /// Maroon paint. 488 | Paint maroon = paintFromRGBA(0x80, 0x00, 0x00); 489 | 490 | /// MediumAquamarine paint. 491 | Paint mediumAquamarine = paintFromRGBA(0x66, 0xcd, 0xaa); 492 | 493 | /// MediumBlue paint. 494 | Paint mediumBlue = paintFromRGBA(0x00, 0x00, 0xcd); 495 | 496 | /// MediumOrchid paint. 497 | Paint mediumOrchid = paintFromRGBA(0xba, 0x55, 0xd3); 498 | 499 | /// MediumPurple paint. 500 | Paint mediumPurple = paintFromRGBA(0x93, 0x70, 0xdb); 501 | 502 | /// MediumSeaGreen paint. 503 | Paint mediumSeaGreen = paintFromRGBA(0x3c, 0xb3, 0x71); 504 | 505 | /// MediumSlateBlue paint. 506 | Paint mediumSlateBlue = paintFromRGBA(0x7b, 0x68, 0xee); 507 | 508 | /// MediumSpringGreen paint. 509 | Paint mediumSpringGreen = paintFromRGBA(0x00, 0xfa, 0x9a); 510 | 511 | /// MediumTurquoise paint. 512 | Paint mediumTurquoise = paintFromRGBA(0x48, 0xd1, 0xcc); 513 | 514 | /// MediumVioletRed paint. 515 | Paint mediumVioletRed = paintFromRGBA(0xc7, 0x15, 0x85); 516 | 517 | /// MidnightBlue paint. 518 | Paint midnightBlue = paintFromRGBA(0x19, 0x19, 0x70); 519 | 520 | /// MintCream paint. 521 | Paint mintCream = paintFromRGBA(0xf5, 0xff, 0xfa); 522 | 523 | /// MistyRose paint. 524 | Paint mistyRose = paintFromRGBA(0xff, 0xe4, 0xe1); 525 | 526 | /// Moccasin paint. 527 | Paint moccasin = paintFromRGBA(0xff, 0xe4, 0xb5); 528 | 529 | /// NavajoWhite paint. 530 | Paint navajoWhite = paintFromRGBA(0xff, 0xde, 0xad); 531 | 532 | /// Navy paint. 533 | Paint navy = paintFromRGBA(0x00, 0x00, 0x80); 534 | 535 | /// OldLace paint. 536 | Paint oldLace = paintFromRGBA(0xfd, 0xf5, 0xe6); 537 | 538 | /// Olive paint. 539 | Paint olive = paintFromRGBA(0x80, 0x80, 0x00); 540 | 541 | /// OliveDrab paint. 542 | Paint oliveDrab = paintFromRGBA(0x6b, 0x8e, 0x23); 543 | 544 | /// Orange paint. 545 | Paint orange = paintFromRGBA(0xff, 0xa5, 0x00); 546 | 547 | /// OrangeRed paint. 548 | Paint orangeRed = paintFromRGBA(0xff, 0x45, 0x00); 549 | 550 | /// Orchid paint. 551 | Paint orchid = paintFromRGBA(0xda, 0x70, 0xd6); 552 | 553 | /// PaleGoldenrod paint. 554 | Paint paleGoldenrod = paintFromRGBA(0xee, 0xe8, 0xaa); 555 | 556 | /// PaleGreen paint. 557 | Paint paleGreen = paintFromRGBA(0x98, 0xfb, 0x98); 558 | 559 | /// PaleTurquoise paint. 560 | Paint paleTurquoise = paintFromRGBA(0xaf, 0xee, 0xee); 561 | 562 | /// PaleVioletRed paint. 563 | Paint paleVioletRed = paintFromRGBA(0xdb, 0x70, 0x93); 564 | 565 | /// PapayaWhip paint. 566 | Paint papayaWhip = paintFromRGBA(0xff, 0xef, 0xd5); 567 | 568 | /// PeachPuff paint. 569 | Paint peachPuff = paintFromRGBA(0xff, 0xda, 0xb9); 570 | 571 | /// Peru paint. 572 | Paint peru = paintFromRGBA(0xcd, 0x85, 0x3f); 573 | 574 | /// Pink paint. 575 | Paint pink = paintFromRGBA(0xff, 0xc0, 0xcb); 576 | 577 | /// Plum paint. 578 | Paint plum = paintFromRGBA(0xdd, 0xa0, 0xdd); 579 | 580 | /// PowderBlue paint. 581 | Paint powderBlue = paintFromRGBA(0xb0, 0xe0, 0xe6); 582 | 583 | /// Purple paint. 584 | Paint purple = paintFromRGBA(0x80, 0x00, 0x80); 585 | 586 | /// RosyBrown paint. 587 | Paint rosyBrown = paintFromRGBA(0xbc, 0x8f, 0x8f); 588 | 589 | /// RoyalBlue paint. 590 | Paint royalBlue = paintFromRGBA(0x41, 0x69, 0xe1); 591 | 592 | /// SaddleBrown paint. 593 | Paint saddleBrown = paintFromRGBA(0x8b, 0x45, 0x13); 594 | 595 | /// Salmon paint. 596 | Paint salmon = paintFromRGBA(0xfa, 0x80, 0x72); 597 | 598 | /// SandyBrown paint. 599 | Paint sandyBrown = paintFromRGBA(0xf4, 0xa4, 0x60); 600 | 601 | /// SeaGreen paint. 602 | Paint seaGreen = paintFromRGBA(0x2e, 0x8b, 0x57); 603 | 604 | /// Seashell paint. 605 | Paint seashell = paintFromRGBA(0xff, 0xf5, 0xee); 606 | 607 | /// Sienna paint. 608 | Paint sienna = paintFromRGBA(0xa0, 0x52, 0x2d); 609 | 610 | /// Silver paint. 611 | Paint silver = paintFromRGBA(0xc0, 0xc0, 0xc0); 612 | 613 | /// SkyBlue paint. 614 | Paint skyBlue = paintFromRGBA(0x87, 0xce, 0xeb); 615 | 616 | /// SlateBlue paint. 617 | Paint slateBlue = paintFromRGBA(0x6a, 0x5a, 0xcd); 618 | 619 | /// SlateGray paint. 620 | Paint slateGray = paintFromRGBA(0x70, 0x80, 0x90); 621 | 622 | /// SlateGrey paint. 623 | Paint slateGrey = paintFromRGBA(0x70, 0x80, 0x90); 624 | 625 | /// Snow paint. 626 | Paint snow = paintFromRGBA(0xff, 0xfa, 0xfa); 627 | 628 | /// SpringGreen paint. 629 | Paint springGreen = paintFromRGBA(0x00, 0xff, 0x7f); 630 | 631 | /// SteelBlue paint. 632 | Paint steelBlue = paintFromRGBA(0x46, 0x82, 0xb4); 633 | 634 | /// Tan paint. 635 | Paint tan = paintFromRGBA(0xd2, 0xb4, 0x8c); 636 | 637 | /// Teal paint. 638 | Paint teal = paintFromRGBA(0x00, 0x80, 0x80); 639 | 640 | /// Thistle paint. 641 | Paint thistle = paintFromRGBA(0xd8, 0xbf, 0xd8); 642 | 643 | /// Tomato paint. 644 | Paint tomato = paintFromRGBA(0xff, 0x63, 0x47); 645 | 646 | /// Turquoise paint. 647 | Paint turquoise = paintFromRGBA(0x40, 0xe0, 0xd0); 648 | 649 | /// Violet paint. 650 | Paint violet = paintFromRGBA(0xee, 0x82, 0xee); 651 | 652 | /// Wheat paint. 653 | Paint wheat = paintFromRGBA(0xf5, 0xde, 0xb3); 654 | 655 | /// WhiteSmoke paint. 656 | Paint whiteSmoke = paintFromRGBA(0xf5, 0xf5, 0xf5); 657 | 658 | /// Yellow paint. 659 | Paint yellow = paintFromRGBA(0xff, 0xff, 0x00); 660 | 661 | /// YellowGreen paint. 662 | Paint yellowGreen = paintFromRGBA(0x9a, 0xcd, 0x32); 663 | -------------------------------------------------------------------------------- /src/ui/picture.d: -------------------------------------------------------------------------------- 1 | /** 2 | * Module for picture manipulation. 3 | * 4 | * Authors: 5 | * Jacob Jensen 6 | * License: 7 | * https://github.com/PoisonEngine/poison-ui/blob/master/LICENSE 8 | */ 9 | module poison.ui.picture; 10 | 11 | import dsfml.graphics : Image, Texture, RenderWindow; 12 | 13 | import poison.ui.paint; 14 | import poison.ui.sprite; 15 | import poison.core : Size, Point; 16 | 17 | /// Paint graphics for a picture. 18 | private class PictureGraphics { 19 | /// The position. 20 | Point _position; 21 | /// The size. 22 | Size _size; 23 | /// The paint. 24 | Paint _paint; 25 | 26 | /** 27 | * Creates a new picture graphic. 28 | * Params: 29 | * position = The position of the graphic. 30 | * size = The size of the graphic. 31 | * paint = The paint of the graphic. 32 | */ 33 | this(Point position, Size size, Paint paint) { 34 | _position = position; 35 | _size = size; 36 | _paint = paint; 37 | } 38 | 39 | @property { 40 | /// Gets the position of the graphic. 41 | Point position() { return _position; } 42 | 43 | /// Gets the size of the graphic. 44 | Size size() { return _size; } 45 | 46 | /// Gets the paint of the graphic. 47 | Paint paint() { return _paint; } 48 | } 49 | } 50 | 51 | /// A picture for image manipulation. 52 | final class Picture { 53 | private: 54 | /// The background sprite. 55 | TextureSprite _backgroundSprite; 56 | 57 | /// The drawing sprite. 58 | TextureSprite _drawingSprite; 59 | 60 | /// The original filename. 61 | string _fileName; 62 | 63 | /// The size. 64 | Size _size; 65 | 66 | /// The fill paint. 67 | Paint _fillPaint; 68 | 69 | /// The image buffer. 70 | ubyte[] _imageBuffer; 71 | 72 | /// The graphics to render onto it. 73 | PictureGraphics[] _graphics; 74 | 75 | /// The position. 76 | Point _position; 77 | 78 | /// Renders the picture. 79 | void render() { 80 | assert(_fileName || _imageBuffer || _size); 81 | 82 | Image backgroundImage; 83 | auto drawingImage = new Image(); 84 | 85 | if (_fileName || _imageBuffer) { 86 | backgroundImage = new Image(); 87 | 88 | if (_fileName) { 89 | backgroundImage.loadFromFile(_fileName); 90 | } 91 | else { 92 | backgroundImage.loadFromMemory(_imageBuffer); 93 | } 94 | 95 | auto imgSize = backgroundImage.getSize(); 96 | _size = new Size(cast(size_t)imgSize.x, cast(size_t)imgSize.y); 97 | 98 | drawingImage.create(_size.width, _size.height, transparent.sfmlColor); 99 | } 100 | else if (_size) { 101 | drawingImage.create(_size.width, _size.height, _fillPaint.sfmlColor); 102 | } 103 | 104 | if (_graphics) { 105 | foreach (graphic; _graphics) { 106 | auto xFrom = graphic.position.x; 107 | auto xTo = graphic.position.x + graphic.size.width; 108 | auto yFrom = graphic.position.y; 109 | auto yTo = graphic.position.y + graphic.size.height; 110 | auto color = graphic.paint.sfmlColor; 111 | 112 | foreach (x; xFrom .. xTo) { 113 | foreach (y; yFrom .. yTo) { 114 | drawingImage.setPixel(cast(uint)x, cast(uint)y, color); 115 | } 116 | } 117 | } 118 | } 119 | 120 | if (backgroundImage) { 121 | auto texture = new Texture(); 122 | texture.loadFromImage(backgroundImage); 123 | texture.setSmooth(true); 124 | 125 | _backgroundSprite = new TextureSprite(texture); 126 | } 127 | 128 | if (drawingImage) { 129 | auto texture = new Texture(); 130 | texture.loadFromImage(drawingImage); 131 | texture.setSmooth(true); 132 | 133 | _drawingSprite = new TextureSprite(texture); 134 | } 135 | 136 | position = _position; 137 | } 138 | 139 | public: 140 | final: 141 | /** 142 | * Creates a new picture. 143 | * Params: 144 | * imageFile = The file for an image to load onto the picture. 145 | */ 146 | this(string imageFile) { 147 | _fileName = imageFile; 148 | } 149 | 150 | /** 151 | * Creates a new picture. 152 | * Params: 153 | * size = The size of the picture. 154 | * fillPaint = The initialization paint. 155 | */ 156 | this(Size size, Paint fillPaint) { 157 | _size = size; 158 | _fillPaint = fillPaint; 159 | } 160 | 161 | /** 162 | * Creates a new picture. 163 | * Params: 164 | * imageBuffer = The buffer to load the picture from. 165 | */ 166 | this(ubyte[] imageBuffer) { 167 | _imageBuffer = imageBuffer; 168 | } 169 | 170 | /** 171 | * Creates a new picture, copied from another. 172 | * Params: 173 | * copyImage = The image to copy. 174 | */ 175 | this(Picture copyImage) { 176 | if (copyImage._imageBuffer) { 177 | this(copyImage._imageBuffer.dup); 178 | } 179 | else if (copyImage._fileName) { 180 | this(copyImage._fileName.dup); 181 | } 182 | else { 183 | this(new Size(copyImage._size.width, copyImage._size.height), copyImage._fillPaint); 184 | } 185 | 186 | if (copyImage._graphics) { 187 | _graphics = copyImage._graphics.dup; 188 | } 189 | } 190 | 191 | /// Clears the graphics. 192 | void clearGraphics() { 193 | _graphics = null; 194 | } 195 | 196 | /** 197 | * Draws paint onto the picture. 198 | * Params: 199 | * position = The position within the picture to draw the paint. 200 | * size = The size of the paint. 201 | * paint = The paint to draw with. 202 | */ 203 | void draw(Point position, Size size, Paint paint) { 204 | _graphics ~= new PictureGraphics(position, size, paint); 205 | } 206 | 207 | /** 208 | * Paints a horizontal gradient on the picture. 209 | * Params: 210 | * position = The position within the picture to paint the gradient. 211 | * size = The size of the gradient. 212 | * from = The paint to gradient from. 213 | * to = The paint to gradient to. 214 | * a = The alpha channel of the gradient. 215 | */ 216 | void gradientHorizontal(Point position, Size size, Paint from, Paint to, ubyte a = 0xff) { 217 | bool decreaseR = from.r > to.r; 218 | bool decreaseG = from.g > to.g; 219 | bool decreaseB = from.b > to.b; 220 | 221 | auto block = new Size(1, size.height); 222 | 223 | ubyte rInc = cast(ubyte)(cast(float)(decreaseR ? from.r - to.r : to.r - from.r) / (cast(float)size.width / 1.5)); 224 | ubyte gInc = cast(ubyte)(cast(float)(decreaseG ? from.g - to.g : to.g - from.g) / (cast(float)size.width / 1.5)); 225 | ubyte bInc = cast(ubyte)(cast(float)(decreaseB ? from.b - to.b : to.b - from.b) / (cast(float)size.width / 1.5)); 226 | 227 | for (auto x = position.x; x < (position.x + size.width); x++) { 228 | draw(new Point(x, position.y), block, paintFromRGBA(from.r, from.g, from.b, a)); 229 | 230 | if (decreaseR && from.r > (to.r + rInc)) { 231 | from.r -= rInc; 232 | } 233 | else if (!decreaseR && from.r < (to.r - rInc)) { 234 | from.r += rInc; 235 | } 236 | 237 | if (decreaseG && from.g > (to.g + gInc)) { 238 | from.g -= gInc; 239 | } 240 | else if (!decreaseG && from.g < (to.g - gInc)) { 241 | from.g += gInc; 242 | } 243 | 244 | if (decreaseB && from.b > (to.b + bInc)) { 245 | from.b -= bInc; 246 | } 247 | else if (!decreaseB && from.b < (to.b - bInc)) { 248 | from.b += bInc; 249 | } 250 | } 251 | } 252 | 253 | /** 254 | * Paints a vertical gradient on the picture. 255 | * Params: 256 | * position = The position within the picture to paint the gradient. 257 | * size = The size of the gradient. 258 | * from = The paint to gradient from. 259 | * to = The paint to gradient to. 260 | * a = The alpha channel of the gradient. 261 | */ 262 | void gradientVertical(Point position, Size size, Paint from, Paint to, ubyte a = 0xff) { 263 | bool decreaseR = from.r > to.r; 264 | bool decreaseG = from.g > to.g; 265 | bool decreaseB = from.b > to.b; 266 | 267 | auto block = new Size(size.width, 1); 268 | 269 | ubyte rInc = cast(ubyte)(cast(float)(decreaseR ? from.r - to.r : to.r - from.r) / (cast(float)size.height / 1.5)); 270 | ubyte gInc = cast(ubyte)(cast(float)(decreaseG ? from.g - to.g : to.g - from.g) / (cast(float)size.height / 1.5)); 271 | ubyte bInc = cast(ubyte)(cast(float)(decreaseB ? from.b - to.b : to.b - from.b) / (cast(float)size.height / 1.5)); 272 | 273 | for (auto y = position.y; y < (position.y + size.height); y++) { 274 | draw(new Point(position.x, y), block, paintFromRGBA(from.r, from.g, from.b, a)); 275 | 276 | if (decreaseR && from.r > (to.r + rInc)) { 277 | from.r -= rInc; 278 | } 279 | else if (!decreaseR && from.r < (to.r - rInc)) { 280 | from.r += rInc; 281 | } 282 | 283 | if (decreaseG && from.g > (to.g + gInc)) { 284 | from.g -= gInc; 285 | } 286 | else if (!decreaseG && from.g < (to.g - gInc)) { 287 | from.g += gInc; 288 | } 289 | 290 | if (decreaseB && from.b > (to.b + bInc)) { 291 | from.b -= bInc; 292 | } 293 | else if (!decreaseB && from.b < (to.b - bInc)) { 294 | from.b += bInc; 295 | } 296 | } 297 | } 298 | 299 | /** 300 | * Resizes the picture. 301 | * Params: 302 | * newSize = Resizes the picture. 303 | * Bug: 304 | * Currently only work for pictures with no image file. 305 | * The fix is to use scale(). 306 | */ 307 | void resize(Size newSize) { 308 | _size = newSize; 309 | } 310 | 311 | /// Finalizes the picture and its graphics. 312 | void finalize() { 313 | render(); 314 | } 315 | 316 | @property { 317 | /// Gets the size of the picture. 318 | Size size() { return _size; } 319 | 320 | /// Gets the file name. 321 | string fileName() { return _fileName; } 322 | 323 | /// Sets the file name. 324 | void fileName(string newFileName) { 325 | _fileName = newFileName; 326 | } 327 | 328 | /// Gets the image buffer. 329 | ubyte[] imageBuffer() { return _imageBuffer; } 330 | 331 | /// Sets the image buffer. 332 | void imageBuffer(ubyte[] imageBuffer) { 333 | _imageBuffer = imageBuffer; 334 | } 335 | } 336 | 337 | package(poison): 338 | @property { 339 | /// Gets the background sprite of the picture. 340 | TextureSprite backgroundSprite() { return _backgroundSprite; } 341 | 342 | /// Gets the drawing sprite of the picture. 343 | TextureSprite drawingSprite() { return _drawingSprite; } 344 | 345 | /// Sets the position of the picture's sprite. 346 | void position(Point newPosition) { 347 | _position = newPosition; 348 | 349 | if (_position) { 350 | if (_backgroundSprite) { 351 | _backgroundSprite.position = _position; 352 | } 353 | 354 | if (_drawingSprite) { 355 | _drawingSprite.position = _position; 356 | } 357 | } 358 | } 359 | } 360 | } 361 | -------------------------------------------------------------------------------- /src/ui/space.d: -------------------------------------------------------------------------------- 1 | /** 2 | * Module for space and dimension manipulation. 3 | * 4 | * Authors: 5 | * Jacob Jensen 6 | * License: 7 | * https://github.com/PoisonEngine/poison-ui/blob/master/LICENSE 8 | */ 9 | module poison.ui.space; 10 | 11 | import poison.core : Point, Size, Edge, Location, EventObserver, ChangeEventArgs; 12 | 13 | /// A wrapper around a space. 14 | class Space : EventObserver { 15 | private: 16 | /// The position. 17 | Point _position; 18 | 19 | /// The size. 20 | Size _size; 21 | 22 | /// The margin. 23 | Edge _margin; 24 | 25 | /// The padding. 26 | Edge _padding; 27 | 28 | public: 29 | /** 30 | * Creates a new space. 31 | * Params: 32 | * position = The position. 33 | * size = The size. 34 | */ 35 | this(Point position, Size size) { 36 | assert(position !is null); 37 | assert(size !is null); 38 | 39 | _position = position; 40 | _size = size; 41 | 42 | _margin = new Edge(0,0,0,0); 43 | _padding = new Edge(0,0,0,0); 44 | } 45 | 46 | @property { 47 | /// Gets the position of the space. 48 | Point position() { return _position; } 49 | 50 | /// Sets the position of the space. 51 | void position(Point newPosition) { 52 | auto oldPosition = _position; 53 | _position = newPosition; 54 | 55 | fireEvent("position", new ChangeEventArgs!Point(oldPosition, _position)); 56 | } 57 | 58 | /// Gets the x coordinate of the space. 59 | ptrdiff_t x() { return _position.x; } 60 | 61 | /// Gets the y coordinate of the space. 62 | ptrdiff_t y() { return _position.y; } 63 | 64 | /// Gets the size of the space. 65 | Size size() { return _size; } 66 | 67 | /// Sets the size of the space. 68 | void size(Size newSize) { 69 | auto oldSize = _size; 70 | _size = newSize; 71 | 72 | fireEvent("size", new ChangeEventArgs!Size(oldSize, _size)); 73 | } 74 | 75 | /// Gets the width of the space. 76 | size_t width() { return _size.width; } 77 | 78 | /// Gets the height of the space. 79 | size_t height() { return _size.height; } 80 | 81 | /// Gets the margin of the space. 82 | Edge margin() { return _margin; } 83 | 84 | /// Sets the margin of the space. 85 | void margin(Edge newMargin) { 86 | auto oldMargin = _margin; 87 | _margin = newMargin; 88 | 89 | fireEvent("margin", new ChangeEventArgs!Edge(oldMargin, _margin)); 90 | } 91 | 92 | /// Gets the top margin of the space. 93 | ptrdiff_t marginTop() { return _margin.top; } 94 | 95 | /// Gets the right margin of the space. 96 | ptrdiff_t marginRight() { return _margin.right; } 97 | 98 | /// Gets the bottom margin of the space. 99 | ptrdiff_t marginBottom() { return _margin.bottom; } 100 | 101 | /// Gets the left margin of the space. 102 | ptrdiff_t marginLeft() { return _margin.left; } 103 | 104 | /// Gets the padding of the space. 105 | Edge padding() { return _padding; } 106 | 107 | /// Sets the padding of the space. 108 | void padding(Edge newPadding) { 109 | auto oldPadding = _padding; 110 | _padding = newPadding; 111 | 112 | fireEvent("padding", new ChangeEventArgs!Edge(oldPadding, _padding)); 113 | } 114 | 115 | /// Gets the top padding of the space. 116 | ptrdiff_t paddingTop() { return _padding.top; } 117 | 118 | /// Gets the right padding of the space. 119 | ptrdiff_t paddingRight() { return _padding.right; } 120 | 121 | /// Gets the bottom padding of the space. 122 | ptrdiff_t paddingBottom() { return _padding.bottom; } 123 | 124 | /// Gets the left padding of the space. 125 | ptrdiff_t paddingLeft() { return _padding.left; } 126 | } 127 | 128 | /** 129 | * Moves the space to another space. 130 | * Params: 131 | * target = The target of the space. 132 | */ 133 | void moveTo(Location location)(Space target) { 134 | assert(target !is null); 135 | 136 | auto newX = target.x; 137 | auto newY = target.y; 138 | 139 | static if (location == Location.northWest) { 140 | newX -= width + target.marginLeft; 141 | newY -= height + target.marginTop; 142 | } 143 | else static if (location == Location.north) { 144 | newX += (target.width / 2) - (width / 2); 145 | newY -= height + target.marginTop; 146 | } 147 | else static if (location == Location.northEast) { 148 | newX += target.width + target.marginRight; 149 | newY -= height + target.marginTop; 150 | } 151 | else static if (location == Location.east) { 152 | newX += target.width + target.marginRight; 153 | newY += (target.height / 2) - (height / 2); 154 | } 155 | else static if (location == Location.southEast) { 156 | newX += target.width + target.marginRight; 157 | newY += target.height + target.marginBottom; 158 | } 159 | else static if (location == Location.south) { 160 | newX += (target.width / 2) - (width / 2); 161 | newY += target.height + target.marginBottom; 162 | } 163 | else static if (location == Location.southWest) { 164 | newX -= width + target.marginLeft; 165 | newY += target.height + target.marginBottom; 166 | } 167 | else static if (location == Location.west) { 168 | newX -= width + target.marginLeft; 169 | newY += (target.height / 2) - (height / 2); 170 | } 171 | else { 172 | static assert(0); 173 | } 174 | 175 | position = new Point(newX, newY); 176 | } 177 | 178 | /** 179 | * Moves the space into another space. 180 | * Params: 181 | * target = The space to move the space into. 182 | */ 183 | void moveIn(Location location)(Space target) { 184 | assert(target !is null); 185 | 186 | auto newX = target.x; 187 | auto newY = target.y; 188 | 189 | static if (location == Location.northWest) { 190 | newX += target.paddingLeft; 191 | newY += target.paddingTop; 192 | } 193 | else static if (location == Location.north) { 194 | newX += (target.width / 2) - (width / 2); 195 | newY += target.paddingTop; 196 | } 197 | else static if (location == Location.northEast) { 198 | newX += target.width - (target.paddingRight + width); 199 | newY += target.paddingTop; 200 | } 201 | else static if (location == Location.east) { 202 | newX += target.width - (target.paddingRight + width); 203 | newY += (target.height / 2) - (height / 2); 204 | } 205 | else static if (location == Location.southEast) { 206 | newX += target.width - (target.paddingRight + width); 207 | newY += target.height - (target.paddingBottom + height); 208 | } 209 | else static if (location == Location.south) { 210 | newX += (target.width / 2) - (width / 2); 211 | newY += target.height - (target.paddingBottom + height); 212 | } 213 | else static if (location == Location.southWest) { 214 | newX += target.paddingLeft; 215 | newY += target.height - (target.paddingBottom + height); 216 | } 217 | else static if (location == Location.west) { 218 | newX += target.paddingLeft; 219 | newY += (target.height / 2) - (height / 2); 220 | } 221 | else { 222 | static assert(0); 223 | } 224 | 225 | position = new Point(newX, newY); 226 | } 227 | 228 | /** 229 | * Centers the x coordinate of the space relative to another space. 230 | * Params: 231 | * target = The target to be relative to. 232 | */ 233 | void centerX(Space target) { 234 | position = new Point((target.width / 2) - (width / 2), y); 235 | } 236 | 237 | /** 238 | * Centers the y coordinate of the space relative to another space. 239 | * Params: 240 | * target = The target to be relative to. 241 | */ 242 | void centerY(Space target) { 243 | position = new Point(x, (target.height / 2) - (height / 2)); 244 | } 245 | 246 | /** 247 | * Centers the space relative to another space. 248 | * Params: 249 | * target = The target to be relative to. 250 | */ 251 | void center(Space target) { 252 | position = new Point((target.width / 2) - (width / 2), (target.height / 2) - (height / 2)); 253 | } 254 | 255 | void moveX(ptrdiff_t amount) { 256 | position = new Point(x + amount, y); 257 | } 258 | 259 | void moveY(ptrdiff_t amount) { 260 | position = new Point(x, y + amount); 261 | } 262 | 263 | /** 264 | * Checks whether the space intersects with a point. 265 | * Params: 266 | * p = The point to check for intersection with. 267 | * Returns: 268 | * If the space intersects with the point. 269 | */ 270 | bool intersect(Point p) { 271 | return (p.x > this.x) && 272 | (p.x < (this.x + cast(ptrdiff_t)this.width)) && 273 | (p.y > this.y) && 274 | (p.y < (this.y + cast(ptrdiff_t)this.height)); 275 | } 276 | 277 | /** 278 | * Checks whether the space intersects with another space. 279 | * Params: 280 | * target = The space to check for intersection with. 281 | * Returns: 282 | * True if the two spaces intersects. 283 | */ 284 | bool intersect(Space target) { 285 | return(target.x < this.x + cast(ptrdiff_t)this.width) && 286 | (this.x < (target.x + cast(ptrdiff_t)target.width)) && 287 | (target.y < this.y + cast(ptrdiff_t)this.height) && 288 | (this.y < target.y + cast(ptrdiff_t)target.height); 289 | } 290 | } 291 | -------------------------------------------------------------------------------- /src/ui/sprite.d: -------------------------------------------------------------------------------- 1 | /** 2 | * Module for texture sprite handling. 3 | * 4 | * Authors: 5 | * Jacob Jensen 6 | * License: 7 | * https://github.com/PoisonEngine/poison-ui/blob/master/LICENSE 8 | */ 9 | module poison.ui.sprite; 10 | 11 | import dsfml.graphics : Sprite, Texture; 12 | import dsfml.system : Vector2f; 13 | 14 | import poison.core : Point; 15 | 16 | /// An extension class to the sprite implementation of Dsfml. 17 | class TextureSprite : Sprite { 18 | private: 19 | /// The position of the sprite. 20 | Point _position; 21 | 22 | public: 23 | /** 24 | * Creates a new texture sprite. 25 | * Params: 26 | * texture = The texture of the sprite. 27 | */ 28 | this(Texture texture) { 29 | super(texture); 30 | } 31 | 32 | @property { 33 | /// Gets the position of the sprite. 34 | Point position() { return _position; } 35 | 36 | /// Sets the position of the sprite. 37 | void position(Point newPosition) { 38 | _position = newPosition; 39 | 40 | super.position = Vector2f(_position.x, _position.y); 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/ui/styles.d: -------------------------------------------------------------------------------- 1 | /** 2 | * Module for style handling/parsing. 3 | * 4 | * Authors: 5 | * Jacob Jensen 6 | * License: 7 | * https://github.com/PoisonEngine/poison-ui/blob/master/LICENSE 8 | */ 9 | module poison.ui.styles; 10 | 11 | import std.file : readText; 12 | import std.json : parseJSON, JSONValue; 13 | import std.conv : to; 14 | import std.array : split, replace; 15 | import std.string : toLower, stripLeft, stripRight; 16 | import std.algorithm : countUntil; 17 | import std.base64 : Base64; 18 | 19 | import poison.ui.graphics; 20 | import poison.ui.paint; 21 | import poison.ui.picture; 22 | import poison.ui.fonts; 23 | import poison.core : Point, Size; 24 | 25 | /// Table of styles. 26 | private Graphics[string] _styles; 27 | 28 | /** 29 | * Gets a style entry. 30 | * Params: 31 | * selector = The selector to find the style for. 32 | */ 33 | Graphics getStyleEntry(string selector) { 34 | return _styles ? _styles.get(selector, null) : null; 35 | } 36 | 37 | /** 38 | * Loads a style sheet. 39 | * Params: 40 | * The style sheet to load. 41 | */ 42 | void loadStyleSheet(string styleSheet) { 43 | auto jsonText = readText(styleSheet); 44 | 45 | loadStyles(jsonText); 46 | } 47 | 48 | /** 49 | * Loads styles from json structured pss. 50 | * Params: 51 | * jsonText = The json. 52 | */ 53 | void loadStyles(string jsonText) { 54 | if (!jsonText) { 55 | return; 56 | } 57 | 58 | auto json = parseJSON("{ " ~ jsonText ~ " }"); 59 | auto entries = json.object; 60 | 61 | if (!entries) { 62 | return; 63 | } 64 | 65 | foreach (selector,entryJson; entries) { 66 | auto styles = entryJson.object; 67 | 68 | if (styles) { 69 | auto graphics = new Graphics(); 70 | 71 | foreach (styleName,styleValue; styles) { 72 | final switch (styleName) { 73 | case "background": { 74 | auto value = styleValue.str; 75 | 76 | handleGroups(graphics, value, (gfx, propertyName, propertyValue) { 77 | final switch (propertyName) { 78 | case "color": { 79 | gfx.backgroundPaint = handlePaintInput(propertyValue); 80 | break; 81 | } 82 | 83 | case "image": { 84 | gfx.backgroundPicture = handleImageInput(graphics, propertyValue); 85 | break; 86 | } 87 | } 88 | }); 89 | break; 90 | } 91 | 92 | case "background-color": { 93 | auto value = styleValue.str; 94 | 95 | graphics.backgroundPaint = handlePaintInput(value); 96 | break; 97 | } 98 | 99 | case "background-image": { 100 | auto value = styleValue.str; 101 | 102 | graphics.backgroundPicture = handleImageInput(graphics, value); 103 | break; 104 | } 105 | 106 | case "foreground-color": { 107 | auto value = styleValue.str; 108 | 109 | graphics.foregroundPaint = handlePaintInput(value); 110 | break; 111 | } 112 | 113 | case "font": { 114 | auto value = styleValue.str; 115 | 116 | handleGroups(graphics, value, (gfx, propertyName, propertyValue) { 117 | final switch (propertyName) { 118 | case "name": { 119 | gfx.font = handleFontInput(propertyValue); 120 | break; 121 | } 122 | 123 | case "path": { 124 | gfx.font = loadFont(propertyValue); 125 | break; 126 | } 127 | 128 | case "size": { 129 | gfx.fontSize = to!uint(propertyValue); 130 | break; 131 | } 132 | } 133 | }); 134 | break; 135 | } 136 | 137 | case "font-name": { 138 | auto value = styleValue.str; 139 | 140 | graphics.font = handleFontInput(value); 141 | break; 142 | } 143 | 144 | case "font-path": { 145 | auto value = styleValue.str; 146 | 147 | graphics.font = loadFont(value); 148 | break; 149 | } 150 | 151 | case "font-size": { 152 | auto value = styleValue.str; 153 | 154 | graphics.fontSize = to!uint(value); 155 | break; 156 | } 157 | 158 | case "size": { 159 | auto size = styleValue.str.split(";"); 160 | 161 | graphics.size = new Size(to!size_t(size[0]), to!size_t(size[1])); 162 | graphics.hasSize = true; 163 | break; 164 | } 165 | 166 | case "position": { 167 | auto position = styleValue.str.split(";"); 168 | 169 | graphics.position = new Point(to!ptrdiff_t(position[0]), to!ptrdiff_t(position[1])); 170 | graphics.hasPosition = true; 171 | break; 172 | } 173 | 174 | case "paint-color": { 175 | auto colors = styleValue.array; 176 | 177 | if (!colors) { 178 | break; 179 | } 180 | 181 | foreach (color; colors) { 182 | Point colorPosition; 183 | Size colorSize; 184 | Paint colorPaint; 185 | 186 | handleGroups(graphics, color.str, (gfx, propertyName, propertyValue) { 187 | final switch (propertyName) { 188 | case "position": { 189 | auto position = propertyValue.split(";"); 190 | 191 | if (position.length != 2) { 192 | break; 193 | } 194 | 195 | colorPosition = new Point(to!ptrdiff_t(position[0]), to!ptrdiff_t(position[1])); 196 | break; 197 | } 198 | 199 | case "size": { 200 | auto size = propertyValue.split(";"); 201 | 202 | if (size.length != 2) { 203 | break; 204 | } 205 | 206 | colorSize = new Size(to!size_t(size[0]), to!size_t(size[1])); 207 | break; 208 | } 209 | 210 | case "color": { 211 | colorPaint = handlePaintInput(propertyValue); 212 | break; 213 | } 214 | } 215 | }); 216 | 217 | graphics.finalizePaint(colorPosition, colorSize, colorPaint); 218 | } 219 | break; 220 | } 221 | 222 | case "paint-gradient-hoz": { 223 | handleGradientInput(styleValue, graphics, false); 224 | break; 225 | } 226 | 227 | case "paint-gradient-ver": { 228 | handleGradientInput(styleValue, graphics, true); 229 | break; 230 | } 231 | } 232 | } 233 | 234 | if (graphics.backgroundPicture) { 235 | graphics.backgroundPicture.finalize(); 236 | } 237 | 238 | _styles[selector] = graphics; 239 | } 240 | } 241 | 242 | import poison.core : Application; 243 | 244 | auto app = Application.app; 245 | 246 | if (!app) { 247 | return; 248 | } 249 | 250 | app.updateStyles(); 251 | } 252 | 253 | private: 254 | /** 255 | * Handles group values. 256 | * Params: 257 | * graphics = The graphics to handle values for. 258 | * value = The value to parse groups from. 259 | * predicate = A predicate to mutate the graphics. 260 | */ 261 | void handleGroups(Graphics graphics, string value, void delegate(Graphics, string, string) predicate) { 262 | assert(predicate !is null); 263 | 264 | auto groups = value.split("|"); 265 | 266 | foreach (group; groups) { 267 | auto separatorIndex = countUntil(group, ":"); 268 | auto propertyName = group[0 .. separatorIndex]; 269 | auto propertyValue = group[separatorIndex + 1 .. $].stripLeft().stripRight(); 270 | 271 | predicate(graphics, propertyName, propertyValue); 272 | } 273 | } 274 | 275 | /** 276 | * Handles group values. 277 | * Params: 278 | * graphics = The graphics to handle values for. 279 | * value = The value to parse groups from. 280 | * predicate = A predicate to mutate the graphics. 281 | */ 282 | void handleGroups(Graphics graphics, string value, void function(Graphics, string, string) predicate) { 283 | assert(predicate !is null); 284 | 285 | auto groups = value.split("|"); 286 | 287 | foreach (group; groups) { 288 | auto separatorIndex = countUntil(group, ":"); 289 | auto propertyName = group[0 .. separatorIndex]; 290 | auto propertyValue = group[separatorIndex + 1 .. $]; 291 | 292 | predicate(graphics, propertyName, propertyValue); 293 | } 294 | } 295 | 296 | /** 297 | * Handles gradient input. 298 | * Params: 299 | * styleValue = The value of the style input. 300 | * graphics = The graphics. 301 | * isVertical = Boolean determining whether the gradient is vertical or not. 302 | */ 303 | void handleGradientInput(JSONValue styleValue, Graphics graphics, bool isVertical) { 304 | auto gradients = styleValue.array; 305 | 306 | if (!gradients) { 307 | return; 308 | } 309 | 310 | foreach (gradient; gradients) { 311 | Point gradientPosition; 312 | Size gradientSize; 313 | Paint fromColorPaint; 314 | Paint toColorPaint; 315 | 316 | handleGroups(graphics, gradient.str, (gfx, propertyName, propertyValue) { 317 | final switch (propertyName) { 318 | case "position": { 319 | auto position = propertyValue.split(";"); 320 | 321 | if (position.length != 2) { 322 | break; 323 | } 324 | 325 | gradientPosition = new Point(to!ptrdiff_t(position[0]), to!ptrdiff_t(position[1])); 326 | break; 327 | } 328 | 329 | case "size": { 330 | auto size = propertyValue.split(";"); 331 | 332 | if (size.length != 2) { 333 | break; 334 | } 335 | 336 | gradientSize = new Size(to!size_t(size[0]), to!size_t(size[1])); 337 | break; 338 | } 339 | 340 | case "fromColor": { 341 | fromColorPaint = handlePaintInput(propertyValue); 342 | break; 343 | } 344 | 345 | case "toColor": { 346 | toColorPaint = handlePaintInput(propertyValue); 347 | break; 348 | } 349 | } 350 | }); 351 | 352 | if (isVertical) { 353 | graphics.finalizeGradientVertical(gradientPosition, gradientSize, fromColorPaint, toColorPaint); 354 | } 355 | else { 356 | graphics.finalizeGradientHorizontal(gradientPosition, gradientSize, fromColorPaint, toColorPaint); 357 | } 358 | } 359 | } 360 | 361 | /** 362 | * Handles paint input. 363 | * Params: 364 | * value = The value of the paint input. 365 | * Returns: 366 | * The generated paint from the value. 367 | */ 368 | Paint handlePaintInput(string value) { 369 | if (value[0] == '#') { 370 | value = value[1 .. $].toLower(); 371 | 372 | if (value.length == 3 && value[0] == value[1] && value[0] == value[2]) { 373 | value ~= value; 374 | } 375 | 376 | if (value.length == 6) { 377 | value ~= "ff"; 378 | } 379 | 380 | auto values = [value[0 .. 2], value[2 .. 4], value[4 .. 6], value[6 .. $]]; 381 | 382 | ubyte r = to!ubyte(values[0], 16); 383 | ubyte g = to!ubyte(values[1], 16); 384 | ubyte b = to!ubyte(values[2], 16); 385 | ubyte a = to!ubyte(values[3], 16); 386 | 387 | return paintFromRGBA(r, g, b, a); 388 | } 389 | else { 390 | auto values = value.split(";"); 391 | 392 | if (values.length > 1) { 393 | assert(values.length == 3 || values.length == 4); 394 | 395 | ubyte r = to!ubyte(values[0]); 396 | ubyte g = to!ubyte(values[1]); 397 | ubyte b = to!ubyte(values[2]); 398 | ubyte a = 0xff; 399 | 400 | if (values.length == 4) { 401 | a = to!ubyte(values[3]); 402 | } 403 | 404 | return paintFromRGBA(r, g, b, a); 405 | } 406 | 407 | return paintFromName(value); 408 | } 409 | } 410 | 411 | /** 412 | * Handles image inputs. 413 | * Params: 414 | * graphics = The graphics. 415 | * value = The image input. 416 | * Returns: 417 | * The generated picture from the value. 418 | */ 419 | Picture handleImageInput(Graphics graphics, string value) { 420 | auto data = value.split(":"); 421 | 422 | if (data.length == 1) { 423 | if (graphics.backgroundPicture) { 424 | graphics.backgroundPicture.fileName = value; 425 | return graphics.backgroundPicture; 426 | } 427 | 428 | return new Picture(value); 429 | } 430 | else { 431 | assert(data.length == 2); 432 | 433 | auto propertyName = data[0]; 434 | auto propertyValue = data[1]; 435 | 436 | final switch (propertyName) { 437 | case "base64": { 438 | auto buffer = Base64.decode(propertyValue); 439 | 440 | if (graphics.backgroundPicture) { 441 | graphics.backgroundPicture.imageBuffer = buffer; 442 | return graphics.backgroundPicture; 443 | } 444 | 445 | return new Picture(buffer); 446 | } 447 | } 448 | } 449 | } 450 | 451 | /** 452 | * Handles font input. 453 | * Params: 454 | * value = The font input value. 455 | */ 456 | Font handleFontInput(string value) { 457 | auto values = value.split(";"); 458 | auto fontName = values[0]; 459 | 460 | if (values.length == 2) { 461 | return retrieveFont(fontName, to!FontStyle(values[1])); 462 | } 463 | 464 | return retrieveFont(fontName, FontStyle.normal); 465 | } 466 | -------------------------------------------------------------------------------- /src/ui/window.d: -------------------------------------------------------------------------------- 1 | /** 2 | * Module for window handling. 3 | * 4 | * Authors: 5 | * Jacob Jensen 6 | * License: 7 | * https://github.com/PoisonEngine/poison-ui/blob/master/LICENSE 8 | */ 9 | module poison.ui.window; 10 | 11 | import dsfmlWindow = dsfml.window; 12 | import dsfml.window : Event, Keyboard, Mouse; 13 | 14 | private alias ContextSettings = dsfmlWindow.ContextSettings; 15 | private alias VideoMode = dsfmlWindow.VideoMode; 16 | private alias WindowStyle = dsfmlWindow.Window.Style; 17 | 18 | import poison.core : Point, Size, ActionArgs, KeyEventArgs, MouseEventArgs, TextEventArgs, executeUI; 19 | import poison.ui.container; 20 | import poison.ui.component; 21 | 22 | /// A window component. 23 | class Window : Container { 24 | private: 25 | /// The context settings. 26 | ContextSettings _context; 27 | 28 | /// The video mode. 29 | VideoMode _videoMode; 30 | 31 | /// The render window. 32 | RenderWindow _window; 33 | 34 | /// The key event. 35 | KeyEventArgs _keyEvent; 36 | 37 | /// The mouse event. 38 | MouseEventArgs _mouseEvent; 39 | 40 | /// The text event. 41 | TextEventArgs _textEvent; 42 | 43 | /// The fps for the window. 44 | uint _fps; 45 | 46 | /// Boolean determining whether the cursor is visible or not. 47 | bool _cursorVisible; 48 | 49 | public: 50 | /** 51 | * Creates a new window. 52 | * Params: 53 | * name = The name of the window. 54 | * title = The title of the window. 55 | * size = The size of the window. 56 | * layers = The layers of the window. (Defaults to 1) 57 | */ 58 | this(string name, dstring title, Size size, size_t layers = 1) { 59 | super(name, size, layers); 60 | 61 | _context.antialiasingLevel = 8; 62 | _videoMode = VideoMode(cast(int)size.width, cast(int)size.height); 63 | _keyEvent = new KeyEventArgs(); 64 | _mouseEvent = new MouseEventArgs(); 65 | _textEvent = new TextEventArgs(); 66 | 67 | innerText = title; 68 | _fps = 60; 69 | 70 | addSelector("window"); 71 | } 72 | 73 | @property { 74 | /// Gets the title of the window. 75 | dstring title() { return super.innerText; } 76 | 77 | /// Sets the title of the window. 78 | void title(dstring newTitle) { 79 | super.innerText = newTitle; 80 | 81 | if (_window) { 82 | _window.setTitle(super.innerText); 83 | } 84 | } 85 | 86 | /// Gets a boolean determining whether the window is open or not. 87 | bool isOpen() { 88 | return _window && _window.isOpen(); 89 | } 90 | 91 | /** 92 | * Gets the parent window. 93 | * Returns: 94 | * Always returns "this" where "this" is the window itself. 95 | */ 96 | override Window parentWindow() { 97 | return this; 98 | } 99 | 100 | /// Gets or sets a boolean determining whether the window is hidden or not. 101 | override void hidden(bool isHidden) { 102 | if (_window) { 103 | _window.setVisible(!isHidden); 104 | } 105 | 106 | super.hidden = isHidden; 107 | } 108 | 109 | /// Gets the fps of the window. 110 | uint fps() { return _fps; } 111 | 112 | /// Sets the fps of the window. 113 | void fps(uint newFps) { 114 | _fps = newFps; 115 | 116 | if (_window) { 117 | _window.setFramerateLimit(_fps); 118 | } 119 | } 120 | 121 | /// Gets a boolean determining whether the cursor is visible or not. 122 | bool cursorVisible() { return _cursorVisible; } 123 | 124 | /// Sets a boolean determining whether the cursor is visible or not. 125 | void cursorVisible(bool isCursorVisible) { 126 | _cursorVisible = isCursorVisible; 127 | 128 | _window.setMouseCursorVisible(_cursorVisible); 129 | } 130 | } 131 | 132 | /// Shows the window. 133 | override void show() { 134 | if (!_window) { 135 | _window = new RenderWindow(_videoMode, title, (WindowStyle.Titlebar | WindowStyle.Resize | WindowStyle.Close), _context); 136 | 137 | if (_fps) { 138 | _window.setFramerateLimit(_fps); 139 | } 140 | 141 | _windowComponents[super.id] = this; 142 | } 143 | 144 | super.show(); 145 | } 146 | 147 | /// Closes the window. 148 | void close() { 149 | assert(_window !is null); 150 | 151 | _window.close(); 152 | } 153 | 154 | /** 155 | * Sends a key input through memory. 156 | * Params: 157 | * key = The key to send an input of. 158 | */ 159 | void sendKeyInput(Keyboard.Key key) { 160 | executeUI({ 161 | _keyEvent.press(key); 162 | 163 | if (_windowComponents) { 164 | foreach (component; _windowComponents) { 165 | if (component && !component.disabled) { 166 | component.fireEvent("keyDown", _keyEvent); 167 | } 168 | } 169 | } 170 | 171 | _keyEvent.release(key); 172 | 173 | if (_windowComponents) { 174 | foreach (component; _windowComponents) { 175 | if (component && !component.disabled) { 176 | component.fireEvent("keyUp", _keyEvent); 177 | } 178 | } 179 | } 180 | }); 181 | } 182 | 183 | /** 184 | * Sends a mouse input through memory. 185 | * Params: 186 | * button = The button to send an input of. 187 | * position = The position of the mouse input. 188 | * Note: 189 | * This doesn't change the position of the cursor. Use Window.changeCursorPosition() instead. 190 | */ 191 | void sendMouseInput(Mouse.Button button, Point position) { 192 | executeUI({ 193 | auto lastPosition = _mouseEvent.position; 194 | 195 | _mouseEvent.press(button); 196 | _mouseEvent.position = position; 197 | 198 | if (_windowComponents) { 199 | foreach (component; _windowComponents) { 200 | if (component && !component.disabled) { 201 | component.fireEvent("mouseDown", _mouseEvent); 202 | } 203 | } 204 | } 205 | 206 | _mouseEvent.release(button); 207 | 208 | if (_windowComponents) { 209 | foreach (component; _windowComponents) { 210 | if (component && !component.disabled) { 211 | component.fireEvent("mouseUp", _mouseEvent); 212 | } 213 | } 214 | } 215 | 216 | _mouseEvent.position = lastPosition; 217 | }); 218 | } 219 | 220 | /** 221 | * Changes the cursor position. 222 | * Params: 223 | * position = The position to set the cursor at. 224 | * Note: 225 | * For virtual mouse presses do not use this. Use Window.sendMouseInput() instead. 226 | */ 227 | void changeCursorPosition(Point position) { 228 | executeUI({ 229 | _mouseEvent.position = position; 230 | 231 | import dsfml.system : Vector2i; 232 | Mouse.setPosition(Vector2i(position.x, position.y)); 233 | }); 234 | } 235 | 236 | package(poison): 237 | /// Components for event handling 238 | Component[size_t] _windowComponents; 239 | 240 | /// Processes the window cycle. 241 | void process() { 242 | Event event; 243 | while(_window.pollEvent(event)) { 244 | switch (event.type) { 245 | case Event.EventType.Closed: { 246 | _window.close(); 247 | return; 248 | } 249 | 250 | case Event.EventType.KeyPressed: { 251 | _keyEvent.press(event.key.code); 252 | 253 | if (_windowComponents) { 254 | foreach (component; _windowComponents) { 255 | if (component && !component.disabled) { 256 | component.fireEvent("keyDown", _keyEvent); 257 | } 258 | } 259 | } 260 | 261 | break; 262 | } 263 | 264 | case Event.EventType.KeyReleased: { 265 | _keyEvent.release(event.key.code); 266 | 267 | if (_windowComponents) { 268 | foreach (component; _windowComponents) { 269 | 270 | if (component && !component.disabled) { 271 | component.fireEvent("keyUp", _keyEvent); 272 | } 273 | } 274 | } 275 | 276 | break; 277 | } 278 | 279 | case Event.EventType.MouseButtonPressed: { 280 | _mouseEvent.press(event.mouseButton.button); 281 | 282 | if (_windowComponents) { 283 | foreach (component; _windowComponents) { 284 | if (component && !component.disabled && component.intersect(_mouseEvent.position)) { 285 | component.fireEvent("mouseDown", _mouseEvent); 286 | } 287 | } 288 | } 289 | 290 | break; 291 | } 292 | 293 | case Event.EventType.MouseButtonReleased: { 294 | _mouseEvent.release(event.mouseButton.button); 295 | 296 | if (_windowComponents) { 297 | foreach (component; _windowComponents) { 298 | if (component && !component.disabled && component.intersect(_mouseEvent.position)) { 299 | component.fireEvent("mouseUp", _mouseEvent); 300 | } 301 | } 302 | } 303 | 304 | break; 305 | } 306 | 307 | case Event.EventType.MouseMoved: { 308 | _mouseEvent.position = new Point(event.mouseMove.x, event.mouseMove.y); 309 | 310 | if (_windowComponents) { 311 | foreach (component; _windowComponents) { 312 | if (component) { 313 | component.fireEvent("mouseMoved", _mouseEvent); 314 | } 315 | } 316 | } 317 | 318 | break; 319 | } 320 | 321 | case Event.EventType.TextEntered: { 322 | _textEvent.enter(event.text.unicode); 323 | 324 | if (_windowComponents) { 325 | foreach (component; _windowComponents) { 326 | if (component && !component.disabled) { 327 | component.fireEvent("textEntered", _textEvent); 328 | } 329 | } 330 | } 331 | 332 | break; 333 | } 334 | 335 | default: break; 336 | } 337 | } 338 | 339 | _window.clear(super.graphics.backgroundPaint.sfmlColor); 340 | super.process(_window); 341 | _window.display(); 342 | } 343 | } 344 | --------------------------------------------------------------------------------