├── .gitignore
├── LICENSE
├── README.md
├── dub.sdl
├── example
├── app.d
├── dub.sdl
└── ui
│ ├── main.glade
│ └── panel.glade
└── source
└── gtkui
├── base.d
├── builder.d
├── exception.d
└── package.d
/.gitignore:
--------------------------------------------------------------------------------
1 | .dub
2 | *.a
3 | example/example
4 | dub.selections.json
5 | *.*~
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2017 Oleg Butko (deviator)
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 | ### Gtk UI aux library for using with UI Builder `Glade`
2 |
3 | ```d
4 | import gtk.Button;
5 | import gtk.Window;
6 | import gtkui;
7 |
8 | void main()
9 | {
10 | auto ui = new UI;
11 | ui.addOnQuit({ ui.exitLoop(); });
12 | ui.runLoop();
13 | }
14 |
15 | class UI : MainBuilderUI
16 | {
17 | mixin GtkBuilderHelper;
18 |
19 | @gtkwidget Window mwindow;
20 | @gtkwidget Button addbtn;
21 |
22 | @gtksignal void someAction()
23 | {
24 | /+ do something +/
25 | }
26 |
27 | this()
28 | {
29 | super(import("main.glade"));
30 | addbtn.addOnClicked((b) { /+ do something +/ });
31 | setupMainWindow(mwindow);
32 | }
33 | }
34 | ```
35 |
36 | For more information see small (~130 lines with comments) [example](example/app.d)
37 |
38 | #### Limitations in signal usage
39 |
40 | 1. Signals in glade file should not contain `User data`,
41 | otherwise this will crash program.
42 |
43 | 2. If you want to use parameters from signal method you must be sure of
44 | their count and order, otherwise wrong count or order of parameters
45 | will crash program.
46 |
47 | 3. Parameter of signal method must be gtk pointers (not wraps from gtkD).
48 |
49 | 4. If you want don't use parameters for signal method be sure what this signal
50 | receive only two parameters (gtk widget and user data),
51 | otherwise you should specify parameters.
--------------------------------------------------------------------------------
/dub.sdl:
--------------------------------------------------------------------------------
1 | name "gtkui"
2 | targetType "library"
3 | description "Aux library for writing GTK-interface program"
4 | authors "Oleg Butko (deviator)"
5 | copyright "Copyright © 2017-2018, Oleg Butko"
6 | license "MIT"
7 | dependency "gtk-d:gtkd" version="~>3.9.0"
--------------------------------------------------------------------------------
/example/app.d:
--------------------------------------------------------------------------------
1 | // basic use of gtkui
2 | import std.conv : to;
3 | import std.stdio : stderr;
4 |
5 | // import all needed gtk classes
6 | import gtk.Box; // layout container
7 | import gtk.Button;
8 | import gtk.Label;
9 | import gtk.Widget; // base class for all widgets
10 | import gtk.Window;
11 |
12 | import gtkui;
13 |
14 | void main()
15 | {
16 | auto ui = new UI;
17 | // on close main window exit from gtk.Main loop
18 | ui.addOnQuit({ ui.exitLoop(); });
19 | // run gtk.Main loop
20 | ui.runLoop();
21 | }
22 |
23 | // main window (main ui class)
24 | class UI : MainBuilderUI
25 | {
26 | /+ override method that find `@gtkwidget` fields
27 | and get instances of those fields from builder
28 | override method that find `@gtksignal` methods
29 | and connect those methods in builder
30 | +/
31 | mixin GtkBuilderHelper;
32 |
33 | // widgets definition
34 | @gtkwidget Window mwindow;
35 | @gtkwidget Box vbox;
36 | @gtkwidget Button addbtn;
37 |
38 | @gtksignal void clickAdd(GtkButton* btnptr)
39 | {
40 | new Button(btnptr).setLabel("add " ~ (panels_count+1).to!string);
41 | }
42 |
43 | @gtksignal void setFocus(GtkContainer* c, GtkWidget* e)
44 | {
45 | stderr.writeln(" set focus callback: ",
46 | cast(void*)vbox.getBoxStruct(), " = ",
47 | cast(void*)c, " widget: ", cast(void*)e);
48 | }
49 |
50 | @gtksignal(true) void setFocusSwapped(GtkWidget* e, GtkContainer* c)
51 | {
52 | stderr.writeln("set focus swapped callback: ",
53 | cast(void*)vbox.getBoxStruct(), " = ",
54 | cast(void*)c, " widget: ", cast(void*)e);
55 | }
56 |
57 | uint panels_count;
58 |
59 | this()
60 | {
61 | enum CUSTOMCSS = ""; // you can customize ui
62 |
63 | // get "main.glade" from string import paths at compile-time
64 | // you can use run-time loading glade files
65 | super(import("main.glade"), CUSTOMCSS);
66 |
67 | // widgets automaticaly gets from builder and you
68 | // can use those without any boilereplate code
69 | addbtn.addOnClicked((b)
70 | {
71 | // create new dynamic part of ui and add this to `vbox`
72 | auto tmp = new Panel(panels_count++);
73 | vbox.packEnd(tmp.mainWidget, true, true, 12);
74 | vbox.setFocusChild(tmp.mainWidget);
75 | });
76 |
77 | // add calling `quit()` on hide mwindow
78 | // and call `showAll()` for mwindow
79 | setupMainWindow(mwindow);
80 | }
81 | }
82 |
83 | // part of ui -- `ChildBuilder` have no method for working with
84 | // gtk.Main and used as part of main ui, but have own builder
85 | // and requires a glade file in ctor
86 | class Panel : ChildBuilderUI
87 | {
88 | mixin GtkBuilderHelper;
89 |
90 | uint idx;
91 |
92 | // child parts
93 | Foo!"g1" g1;
94 | Foo!"g2" g2;
95 |
96 | @gtkwidget Box panelmainbox;
97 |
98 | // as for widgets signals can have namespace
99 | // at glade file signal should have name "panel.clickG2"
100 | @gtksignal("panel") void clickG2()
101 | {
102 | stderr.writefln("clickG2 signal (from %d)", idx);
103 | }
104 |
105 | this(uint idx)
106 | {
107 | this.idx = idx;
108 | super(import("panel.glade"));
109 | g1 = new typeof(g1)(this);
110 | g2 = new typeof(g2)(this);
111 | }
112 |
113 | override Widget mainWidget() @property { return panelmainbox; }
114 | }
115 |
116 | // `ChildGtkUI` most simplest implementation of GtkUI and can't be used
117 | // independently of some `GtkUI` that provides `getObject` method as parent
118 | class Foo(string NAME) : ChildGtkUI
119 | {
120 | mixin GtkUIHelper;
121 |
122 | // you can use prefix in glade file for widget names ("g1", and "g2")
123 | @gtkwidget(NAME)
124 | {
125 | Button btn; // "g1.btn" or "g2.btn"
126 | Label label; // "g1.label" or "g2.label"
127 | }
128 |
129 | this(GtkUI parent)
130 | {
131 | super(parent);
132 | btn.addOnClicked((b) // some behavior
133 | { label.setText((label.getText.to!uint + 1).to!string); });
134 | }
135 | }
--------------------------------------------------------------------------------
/example/dub.sdl:
--------------------------------------------------------------------------------
1 | name "example"
2 | targetType "executable"
3 | stringImportPaths "ui"
4 | dependency "gtkui" path="../"
5 | sourceFiles "app.d"
--------------------------------------------------------------------------------
/example/ui/main.glade:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
51 |
52 |
--------------------------------------------------------------------------------
/example/ui/panel.glade:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | True
7 | False
8 | gtk-go-up
9 |
10 |
11 | True
12 | False
13 | gtk-go-up
14 |
15 |
16 | True
17 | False
18 | 6
19 | 6
20 | 6
21 | 6
22 | vertical
23 | 12
24 |
25 |
26 | True
27 | False
28 | 0
29 | in
30 |
31 |
32 | True
33 | False
34 | 6
35 | 6
36 | 6
37 | 6
38 | 12
39 |
40 |
41 | True
42 | False
43 | 1
44 |
45 |
46 | True
47 | True
48 | 0
49 |
50 |
51 |
52 |
53 | True
54 | True
55 | True
56 | image1
57 | True
58 |
59 |
60 | False
61 | True
62 | 1
63 |
64 |
65 |
66 |
67 |
68 |
69 | True
70 | False
71 | g1
72 |
73 |
74 |
75 |
76 | False
77 | True
78 | 0
79 |
80 |
81 |
82 |
83 | True
84 | False
85 | 0
86 | in
87 |
88 |
89 | True
90 | False
91 | 6
92 | 6
93 | 6
94 | 6
95 | vertical
96 | 12
97 |
98 |
99 | True
100 | True
101 | True
102 | image2
103 | True
104 |
105 |
106 |
107 | False
108 | True
109 | 0
110 |
111 |
112 |
113 |
114 | True
115 | False
116 | 1
117 |
118 |
119 | False
120 | True
121 | 1
122 |
123 |
124 |
125 |
126 |
127 |
128 | True
129 | False
130 | g2
131 |
132 |
133 |
134 |
135 | False
136 | True
137 | 1
138 |
139 |
140 |
141 |
142 |
--------------------------------------------------------------------------------
/source/gtkui/base.d:
--------------------------------------------------------------------------------
1 | /// Base types for creating GUI controllers
2 | module gtkui.base;
3 |
4 | import gtkui.exception;
5 |
6 | import gobject.ObjectG;
7 |
8 | /// Base GUI interface type
9 | interface GtkUI
10 | {
11 | /// For `@gtkwidget` UDA
12 | protected struct WidgetUDA { string ns; }
13 | /// ditto
14 | static protected auto gtkwidget(string ns="") @property
15 | { return WidgetUDA(ns); }
16 |
17 | /// Automatic initialize `@gtkwidget` fields
18 | protected void setUpGtkWidgetFields();
19 | ///
20 | protected ObjectG getObject(string name);
21 |
22 | /++ Returns:
23 | casted ui object by name (widget for example)
24 | +/
25 | auto ui(T)(string name, string file=__FILE__, size_t line=__LINE__)
26 | if (is(T : ObjectG))
27 | {
28 | import std.format : format;
29 | import std.exception : enforce;
30 | auto obj = enforce(getObject(name),
31 | new GUIException(format("can't get object '%s'", name), file, line));
32 |
33 | return enforce(cast(T)obj,
34 | new GUIException(format("object '%s' has type '%s', not '%s'",
35 | name, obj.getType(), typeid(T).name), file, line));
36 | }
37 |
38 | /++ Insert this mixin in all child classes where used `@gtkwidget`
39 | contains:
40 | implementation of `void setUpGtkWidgetFields()`
41 | +/
42 | mixin template GtkUIHelper()
43 | {
44 | protected override void setUpGtkWidgetFields()
45 | {
46 | import std.traits : hasUDA, getUDAs;
47 | import std.format : format;
48 | import gobject.ObjectG;
49 |
50 | alias T = typeof(this);
51 |
52 | foreach (m; __traits(allMembers, typeof(this)))
53 | {
54 | static if (hasUDA!(__traits(getMember, T, m), WidgetUDA))
55 | {
56 | enum uda = getUDAs!(__traits(getMember, T, m), WidgetUDA)[0];
57 | enum name = (uda.ns.length ? uda.ns ~ "." : "") ~ m;
58 | alias F = typeof(__traits(getMember, T, m));
59 | static assert(is(F : ObjectG), format("%s is not an ObjectG", F.stringof));
60 | __traits(getMember, T, m) = ui!F(name);
61 | }
62 | }
63 | }
64 | }
65 | }
66 |
67 | /// Need parent `GtkUI` for working
68 | class ChildGtkUI : GtkUI
69 | {
70 | mixin GtkUIHelper;
71 |
72 | ///
73 | protected GtkUI parent;
74 |
75 | ///
76 | this(GtkUI parent)
77 | {
78 | this.parent = parent;
79 | setUpGtkWidgetFields();
80 | }
81 |
82 | ///
83 | override ObjectG getObject(string name)
84 | { return parent.getObject(name); }
85 | }
--------------------------------------------------------------------------------
/source/gtkui/builder.d:
--------------------------------------------------------------------------------
1 | /// Use gtk.Builder for loading interface
2 | module gtkui.builder;
3 |
4 | import std.experimental.logger;
5 | import std.exception : enforce;
6 |
7 | import gtk.Builder;
8 | import gtk.Widget;
9 | import gtk.Window;
10 | import gobject.ObjectG;
11 |
12 | import gtkui.base;
13 | import gtkui.exception;
14 |
15 | ///
16 | abstract class BuilderUI : GtkUI
17 | {
18 | /// For `@gtksignal` UDA
19 | protected struct SignalUDA { string ns; bool swapped; }
20 | /// ditto
21 | static protected auto gtksignal(string ns="", bool swapped=false) @property
22 | { return SignalUDA(ns, swapped); }
23 | /// ditto
24 | static protected auto gtksignal(bool swapped) @property
25 | { return SignalUDA("", swapped); }
26 |
27 | mixin GtkUIHelper;
28 |
29 | /// parse xml ui and create elements
30 | protected Builder builder;
31 |
32 | /++ Insert this mixin in all builder classes where need use signals
33 | contains:
34 | implementation of `void setUpGtkSignals()`
35 | static extern(C) callback's for signals
36 | +/
37 | mixin template GtkBuilderHelper()
38 | {
39 | mixin GtkUIHelper;
40 |
41 | import std.traits : hasUDA;
42 |
43 | alias This = typeof(this);
44 |
45 | private static string[] signatureNames(string m)()
46 | {
47 | import std.format : format;
48 | alias F = __traits(getMember, This, m);
49 | string[] ret;
50 | foreach (i, p; Parameters!F)
51 | ret ~= format!"p%d"(i);
52 | return ret;
53 | }
54 |
55 | import std.string : join;
56 | import std.traits : Parameters;
57 |
58 | private static string getSignature(string m)()
59 | {
60 | import std.traits : getUDAs;
61 | import std.algorithm : map;
62 | import std.range : enumerate;
63 | import std.format : format;
64 | enum swapped = getUDAs!(__traits(getMember, This, m), SignalUDA)[0].swapped;
65 | enum sNames = signatureNames!m;
66 | enum params = sNames.length ?
67 | sNames.enumerate.map!(a=>"Parameters!("~m~")["~a.index.to!string~"] "~a.value).join(", ")
68 | : `void* obj`;
69 | return swapped ? `void* user_data, `~params : params~`, void* user_data`;
70 | }
71 |
72 | static foreach (m; __traits(allMembers, This))
73 | {
74 | static if (hasUDA!(__traits(getMember, This, m), SignalUDA))
75 | {
76 | mixin(`protected static extern(C) void __g_signal_callback_`~m~`(`~getSignature!m~`)
77 | {
78 | import gtkui.exception;
79 | import std.exception : enforce;
80 |
81 | auto t = enforce(cast(This)(user_data),
82 | new GUIException("user data pointer for signal '`~m~`' is not '`~This.stringof~`'"));
83 | t.`~m~`(`~signatureNames!(m).join(", ")~`);
84 | }`);
85 | }
86 | }
87 |
88 | protected override void setUpGtkSignals()
89 | {
90 | import std.traits : getUDAs, isCallable;
91 |
92 | static foreach (m; __traits(allMembers, This))
93 | {
94 | static if (hasUDA!(__traits(getMember, This, m), SignalUDA))
95 | {{
96 | static if (!isCallable!(__traits(getMember, This, m)))
97 | static assert(0, "signal can be only callable object (field '"~m~"')");
98 | enum uda = getUDAs!(__traits(getMember, This, m), SignalUDA)[0];
99 | enum name = (uda.ns.length ? uda.ns ~ "." : "") ~ m;
100 | mixin(`builder.addCallbackSymbol(name, cast(GCallback)(&__g_signal_callback_`~m~`));`);
101 | }}
102 | }
103 | builder.connectSignals(cast(void*)this);
104 | }
105 | }
106 |
107 | ///
108 | this(string xml)
109 | {
110 | builder = new Builder;
111 | enforce(builder.addFromString(xml), new GUIException("cannot create ui"));
112 | setUpGtkWidgetFields();
113 | setUpGtkSignals();
114 | }
115 |
116 | ///
117 | protected void setUpGtkSignals() {}
118 |
119 | /// Use builder for getting objects
120 | override ObjectG getObject(string name) { return builder.getObject(name); }
121 | }
122 |
123 | /// Class for main UI controller
124 | class MainBuilderUI : BuilderUI
125 | {
126 | import glib.Idle;
127 | static import gtk.Main;
128 | alias GtkMain = gtk.Main.Main;
129 |
130 | protected:
131 | static bool __gtk_inited = false;
132 |
133 | /// run Main.init with empty args by default
134 | static void initializeGtk(string[] args=[])
135 | {
136 | if (__gtk_inited) return;
137 | __gtk_inited = true;
138 | GtkMain.init(args);
139 | }
140 |
141 | ///
142 | void delegate()[] onQuitList;
143 |
144 | ///
145 | void quit() { foreach(dlg; onQuitList) dlg(); }
146 |
147 | public:
148 |
149 | ///
150 | Idle[] idles;
151 |
152 | ///
153 | void addOnIdle(void delegate() fnc)
154 | { idles ~= new Idle({ fnc(); return true; }); }
155 |
156 | ///
157 | void runLoop() { GtkMain.run(); }
158 |
159 | ///
160 | bool loopStep(bool block=false)
161 | { return GtkMain.iterationDo(block); }
162 |
163 | ///
164 | void exitLoop() { GtkMain.quit(); }
165 |
166 | ///
167 | void addOnQuit(void delegate() dlg) { onQuitList ~= dlg; }
168 |
169 | ///
170 | protected static string lastUsedCss;
171 |
172 | ///
173 | static void updateStyle(string css, bool throwOnError=false)
174 | {
175 | import gtk.CssProvider;
176 | import gdk.Screen;
177 | import gtk.StyleContext;
178 | import std.typecons : scoped;
179 |
180 | if (css != lastUsedCss)
181 | {
182 | lastUsedCss = css;
183 | try
184 | {
185 | auto prov = scoped!CssProvider;
186 | prov.loadFromData(css);
187 |
188 | StyleContext.addProviderForScreen(Screen.getDefault(),
189 | prov, GTK_STYLE_PROVIDER_PRIORITY_USER);
190 | }
191 | catch (Throwable e)
192 | {
193 | .error("error while loading style: ", e.msg);
194 | if (throwOnError) throw e;
195 | }
196 | }
197 | }
198 |
199 | ///
200 | this(string xml, string css="")
201 | {
202 | initializeGtk();
203 | super(xml);
204 | if (css.length)
205 | updateStyle(css);
206 | }
207 |
208 | /++
209 | add on hide calling quit method
210 | +/
211 | void setupMainWindow(Window w)
212 | {
213 | w.addOnHide((Widget){ quit(); });
214 | w.showAll();
215 | }
216 | }
217 |
218 | /// For child UI controllers
219 | class ChildBuilderUI : BuilderUI
220 | {
221 | ///
222 | this(string xml) { super(xml); }
223 |
224 | /// For adding by parent controller
225 | abstract Widget mainWidget() @property;
226 | }
--------------------------------------------------------------------------------
/source/gtkui/exception.d:
--------------------------------------------------------------------------------
1 | ///
2 | module gtkui.exception;
3 |
4 | ///
5 | class GUIException : Exception
6 | {
7 | this(string msg, string file=__FILE__, size_t line=__LINE__)
8 | pure nothrow @nogc @safe
9 | { super(msg, file, line); }
10 | }
--------------------------------------------------------------------------------
/source/gtkui/package.d:
--------------------------------------------------------------------------------
1 | ///
2 | module gtkui;
3 |
4 | public import gtkui.base;
5 | public import gtkui.builder;
6 | public import gtkui.exception;
--------------------------------------------------------------------------------