summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorCyrille Bagard <nocbos@gmail.com>2019-01-23 20:16:14 (GMT)
committerCyrille Bagard <nocbos@gmail.com>2019-01-23 20:16:14 (GMT)
commitd0a287a4e57b299cec22c94b028e3553d5b36fcd (patch)
tree86a274d6c894ec6506f0b5d55436d958a556f9fc
parentf251814cf0fd10a767972530c119f8f109613c48 (diff)
Prepared a toolbar for actions on basic blocks.
-rw-r--r--pixmaps/collapse.pngbin0 -> 206 bytes
-rw-r--r--pixmaps/collapse_dark.pngbin0 -> 216 bytes
-rw-r--r--pixmaps/expand.pngbin0 -> 156 bytes
-rw-r--r--pixmaps/expand_dark.pngbin0 -> 161 bytes
-rw-r--r--pixmaps/palette.pngbin0 -> 565 bytes
-rw-r--r--src/gtkext/Makefile.am18
-rw-r--r--src/gtkext/blockbar.ui97
-rw-r--r--src/gtkext/gresource.xml6
-rw-r--r--src/gtkext/gtkblockdisplay.c2
-rw-r--r--src/gtkext/gtkbufferdisplay-int.h3
-rw-r--r--src/gtkext/gtkbufferdisplay.c156
-rw-r--r--src/gtkext/gtkbufferdisplay.h8
-rw-r--r--src/gtkext/gtkdisplaypanel-int.h4
-rw-r--r--src/gtkext/gtkdisplaypanel.c4
-rw-r--r--src/gui/core/Makefile.am22
-rw-r--r--src/gui/core/gresource.xml10
-rw-r--r--themes/Adwaita/Makefile.am3
-rw-r--r--themes/Adwaita/core.css16
-rw-r--r--themes/Adwaita/widgets-dark.css18
-rw-r--r--themes/Adwaita/widgets.css18
-rw-r--r--tools/icons/collapse.py46
-rw-r--r--tools/icons/expand.py46
-rwxr-xr-xtools/icons/gen.sh9
-rw-r--r--tools/icons/palette.py53
24 files changed, 535 insertions, 4 deletions
diff --git a/pixmaps/collapse.png b/pixmaps/collapse.png
new file mode 100644
index 0000000..3f3d93c
--- /dev/null
+++ b/pixmaps/collapse.png
Binary files differ
diff --git a/pixmaps/collapse_dark.png b/pixmaps/collapse_dark.png
new file mode 100644
index 0000000..4961a78
--- /dev/null
+++ b/pixmaps/collapse_dark.png
Binary files differ
diff --git a/pixmaps/expand.png b/pixmaps/expand.png
new file mode 100644
index 0000000..793b3f9
--- /dev/null
+++ b/pixmaps/expand.png
Binary files differ
diff --git a/pixmaps/expand_dark.png b/pixmaps/expand_dark.png
new file mode 100644
index 0000000..4179f38
--- /dev/null
+++ b/pixmaps/expand_dark.png
Binary files differ
diff --git a/pixmaps/palette.png b/pixmaps/palette.png
new file mode 100644
index 0000000..d4ee16d
--- /dev/null
+++ b/pixmaps/palette.png
Binary files differ
diff --git a/src/gtkext/Makefile.am b/src/gtkext/Makefile.am
index 158e591..24df0c3 100644
--- a/src/gtkext/Makefile.am
+++ b/src/gtkext/Makefile.am
@@ -1,6 +1,11 @@
+BUILT_SOURCES = resources.h resources.c
+
noinst_LTLIBRARIES = libgtkext.la
+UI_FILES = \
+ blockbar.ui
+
libgtkext_la_SOURCES = \
diagram.h diagram.c \
easygtk.h easygtk.c \
@@ -16,6 +21,7 @@ libgtkext_la_SOURCES = \
gtkgraphdisplay.h gtkgraphdisplay.c \
gtkstatusstack.h gtkstatusstack.c \
rendering.h rendering.c \
+ resources.h resources.c \
support.h support.c \
tiledgrid.h tiledgrid.c \
tmgt.h tmgt.c
@@ -36,3 +42,15 @@ AM_CPPFLAGS = $(LIBGTK_CFLAGS) $(LIBXML_CFLAGS)
AM_CFLAGS = $(DEBUG_CFLAGS) $(WARNING_FLAGS) $(COMPLIANCE_FLAGS)
SUBDIRS = graph
+
+
+resources.c: gresource.xml $(UI_FILES)
+ glib-compile-resources --target=$@ --sourcedir=$(srcdir) --generate-source --c-name gtkext gresource.xml
+
+resources.h: gresource.xml
+ glib-compile-resources --target=$@ --sourcedir=$(srcdir) --generate-header --c-name gtkext gresource.xml
+
+
+CLEANFILES = resources.h resources.c
+
+EXTRA_DIST = gresource.xml $(UI_FILES)
diff --git a/src/gtkext/blockbar.ui b/src/gtkext/blockbar.ui
new file mode 100644
index 0000000..b8f07cd
--- /dev/null
+++ b/src/gtkext/blockbar.ui
@@ -0,0 +1,97 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Generated with glade 3.21.0 -->
+<interface>
+ <requires lib="gtk+" version="3.20"/>
+ <object class="GtkOffscreenWindow">
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkGrid" id="blockbar">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkButton">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="focus_on_click">False</property>
+ <property name="receives_default">True</property>
+ <property name="tooltip_text" translatable="yes">Define a background color for the whole block</property>
+ <property name="opacity">0.59999999999999998</property>
+ <property name="relief">none</property>
+ <signal name="enter-notify-event" handler="on_block_bar_enter_notify" swapped="no"/>
+ <signal name="leave-notify-event" handler="on_block_bar_leave_notify" swapped="no"/>
+ <child>
+ <object class="GtkImage">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ </object>
+ </child>
+ <style>
+ <class name="tinybutton"/>
+ <class name="block-palette"/>
+ </style>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="focus_on_click">False</property>
+ <property name="receives_default">True</property>
+ <property name="tooltip_text" translatable="yes">Collapse the block with all its dominated blocks</property>
+ <property name="opacity">0.59999999999999998</property>
+ <property name="relief">none</property>
+ <signal name="enter-notify-event" handler="on_block_bar_enter_notify" swapped="no"/>
+ <signal name="leave-notify-event" handler="on_block_bar_leave_notify" swapped="no"/>
+ <child>
+ <object class="GtkImage">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ </object>
+ </child>
+ <style>
+ <class name="tinybutton"/>
+ <class name="block-collapse"/>
+ </style>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton">
+ <property name="can_focus">True</property>
+ <property name="focus_on_click">False</property>
+ <property name="receives_default">True</property>
+ <property name="tooltip_text" translatable="yes">Expand the collapses block</property>
+ <property name="opacity">0.59999999999999998</property>
+ <property name="relief">none</property>
+ <signal name="enter-notify-event" handler="on_block_bar_enter_notify" swapped="no"/>
+ <signal name="leave-notify-event" handler="on_block_bar_leave_notify" swapped="no"/>
+ <child>
+ <object class="GtkImage">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ </object>
+ </child>
+ <style>
+ <class name="tinybutton"/>
+ <class name="block-expand"/>
+ </style>
+ </object>
+ <packing>
+ <property name="left_attach">2</property>
+ <property name="top_attach">0</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ <child type="titlebar">
+ <placeholder/>
+ </child>
+ </object>
+</interface>
diff --git a/src/gtkext/gresource.xml b/src/gtkext/gresource.xml
new file mode 100644
index 0000000..0250484
--- /dev/null
+++ b/src/gtkext/gresource.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<gresources>
+ <gresource prefix="/org/chrysalide/gtkext">
+ <file compressed="true">blockbar.ui</file>
+ </gresource>
+</gresources>
diff --git a/src/gtkext/gtkblockdisplay.c b/src/gtkext/gtkblockdisplay.c
index 97136c9..546c1aa 100644
--- a/src/gtkext/gtkblockdisplay.c
+++ b/src/gtkext/gtkblockdisplay.c
@@ -429,4 +429,6 @@ void gtk_block_display_override_view_index(GtkBlockDisplay *display, unsigned in
GTK_BUFFER_DISPLAY(display)->offsets.max_widths[BLC_ASSEMBLY_LABEL] = 0;
+ gtk_buffer_display_add_block_bar(GTK_BUFFER_DISPLAY(display));
+
}
diff --git a/src/gtkext/gtkbufferdisplay-int.h b/src/gtkext/gtkbufferdisplay-int.h
index c84f6d5..b4b64d0 100644
--- a/src/gtkext/gtkbufferdisplay-int.h
+++ b/src/gtkext/gtkbufferdisplay-int.h
@@ -49,6 +49,9 @@ struct _GtkBufferDisplay
guint caret_timer; /* Identifiant du chronomètre */
bool show_caret; /* Bascule entre les affichages*/
+ GtkBuilder *builder; /* Constructeur à manipuler */
+ GtkWidget *bar; /* Barre d'outils intégrée */
+
};
/* Composant d'affichage de tampon de lignes (classe) */
diff --git a/src/gtkext/gtkbufferdisplay.c b/src/gtkext/gtkbufferdisplay.c
index 1b452f1..5ad808f 100644
--- a/src/gtkext/gtkbufferdisplay.c
+++ b/src/gtkext/gtkbufferdisplay.c
@@ -108,6 +108,20 @@ static void gtk_buffer_display_draw_caret(GtkBufferDisplay *, cairo_t *);
+/* ------------------------- INCLUSION D'UNE BARRE D'OUTILS ------------------------- */
+
+
+/* Place correctement la barre d'outils pour bloc. */
+static void gtk_buffer_display_move_block_bar(GtkBufferDisplay *);
+
+/* Accompagne le début du survol d'un élément de barre d'outils. */
+static gboolean on_block_bar_enter_notify(GtkWidget *, GdkEventCrossing *, GtkBufferDisplay *);
+
+/* Accompagne la fin du survol d'un élément de barre d'outils. */
+static gboolean on_block_bar_leave_notify(GtkWidget *, GdkEventCrossing *, GtkBufferDisplay *);
+
+
+
/* ---------------------------------------------------------------------------------- */
/* INTERACTION DIRECTE AVEC GTK */
/* ---------------------------------------------------------------------------------- */
@@ -223,6 +237,9 @@ static void gtk_buffer_display_dispose(GtkBufferDisplay *display)
g_clear_object(&display->cursor);
+ g_clear_object(&display->builder);
+ g_clear_object(&display->bar);
+
G_OBJECT_CLASS(gtk_buffer_display_parent_class)->dispose(G_OBJECT(display));
}
@@ -466,6 +483,10 @@ static gboolean gtk_buffer_display_draw(GtkWidget *widget, cairo_t *cr)
cairo_restore(cr);
+ /* Dessin des composants contenus */
+
+ GTK_WIDGET_CLASS(gtk_buffer_display_parent_class)->draw(widget, cr);
+
return FALSE;
}
@@ -565,10 +586,22 @@ static gboolean gtk_buffer_display_key_press(GtkWidget *widget, GdkEventKey *eve
static void gtk_buffer_display_compute_requested_size(GtkBufferDisplay *display, gint *width, gint *height)
{
+ gint extra; /* Eventuel supplément largeur */
+
if (width != NULL)
{
if (display->view != NULL)
+ {
*width = g_buffer_view_get_width(display->view, GTK_DISPLAY_PANEL(display)->options);
+
+ if (display->bar != NULL)
+ {
+ gtk_widget_get_preferred_width(display->bar, NULL, &extra);
+ *width += extra;
+ }
+
+ }
+
else
*width = 0;
}
@@ -1183,3 +1216,126 @@ static void gtk_buffer_display_draw_caret(GtkBufferDisplay *display, cairo_t *cr
}
}
+
+
+
+/* ---------------------------------------------------------------------------------- */
+/* INCLUSION D'UNE BARRE D'OUTILS */
+/* ---------------------------------------------------------------------------------- */
+
+
+/******************************************************************************
+* *
+* Paramètres : display = panneau d'affichage concerné. *
+* *
+* Description : Ajoute une nouvelle barre d'outils pour bloc au composant. *
+* *
+* Retour : - *
+* *
+* Remarques : - *
+* *
+******************************************************************************/
+
+void gtk_buffer_display_add_block_bar(GtkBufferDisplay *display)
+{
+ GtkWidget *bar; /* Barre d'outils à intégrer */
+
+ assert(display->builder == NULL);
+
+ display->builder = gtk_builder_new_from_resource("/org/chrysalide/gtkext/blockbar.ui");
+
+ bar = GTK_WIDGET(gtk_builder_get_object(display->builder, "blockbar"));
+
+ display->bar = bar;
+ g_object_ref(G_OBJECT(bar));
+
+ g_object_ref(G_OBJECT(bar));
+ gtk_widget_unparent(bar);
+
+ gtk_fixed_put(GTK_FIXED(display), bar, 0, 0);
+
+ gtk_builder_add_callback_symbols(display->builder,
+ "on_block_bar_enter_notify", G_CALLBACK(on_block_bar_enter_notify),
+ "on_block_bar_leave_notify", G_CALLBACK(on_block_bar_leave_notify),
+ NULL);
+
+ gtk_builder_connect_signals(display->builder, display);
+
+ gtk_buffer_display_move_block_bar(display);
+
+}
+
+
+/******************************************************************************
+* *
+* Paramètres : display = panneau d'affichage concerné. *
+* *
+* Description : Place correctement la barre d'outils pour bloc. *
+* *
+* Retour : - *
+* *
+* Remarques : - *
+* *
+******************************************************************************/
+
+static void gtk_buffer_display_move_block_bar(GtkBufferDisplay *display)
+{
+ GtkWidget *bar; /* Barre d'outils courante */
+ gint width; /* Largeur requise à vide */
+
+ bar = display->bar;
+ display->bar = NULL;
+
+ gtk_buffer_display_compute_requested_size(display, &width, NULL);
+
+ display->bar = bar;
+
+ gtk_fixed_move(GTK_FIXED(display), bar, width, (int)BORDER_CORNER_RADIUS / 2);
+
+}
+
+
+/******************************************************************************
+* *
+* Paramètres : widget = composant graphique concerné par l'opération. *
+* event = informations liées à l'événement. *
+* display = panneau d'affichage impliqué par l'action. *
+* *
+* Description : Accompagne le début du survol d'un élément de barre d'outils.*
+* *
+* Retour : FALSE pour poursuivre la propagation de l'événement. *
+* *
+* Remarques : - *
+* *
+******************************************************************************/
+
+static gboolean on_block_bar_enter_notify(GtkWidget *widget, GdkEventCrossing *event, GtkBufferDisplay *display)
+{
+ gtk_widget_set_opacity(widget, 1.0);
+
+ return FALSE;
+
+}
+
+
+/******************************************************************************
+* *
+* Paramètres : widget = composant graphique concerné par l'opération. *
+* event = informations liées à l'événement. *
+* display = panneau d'affichage impliqué par l'action. *
+* *
+* Description : Accompagne la fin du survol d'un élément de barre d'outils. *
+* *
+* Retour : FALSE pour poursuivre la propagation de l'événement. *
+* *
+* Remarques : - *
+* *
+******************************************************************************/
+
+static gboolean on_block_bar_leave_notify(GtkWidget *widget, GdkEventCrossing *event, GtkBufferDisplay *display)
+{
+ gtk_widget_set_opacity(widget, 0.6);
+
+ return FALSE;
+
+}
diff --git a/src/gtkext/gtkbufferdisplay.h b/src/gtkext/gtkbufferdisplay.h
index 73fbbde..6c6c8b5 100644
--- a/src/gtkext/gtkbufferdisplay.h
+++ b/src/gtkext/gtkbufferdisplay.h
@@ -67,4 +67,12 @@ bool gtk_buffer_display_move_caret_to(GtkBufferDisplay *, bool, gint *);
+/* ------------------------- INCLUSION D'UNE BARRE D'OUTILS ------------------------- */
+
+
+/* Ajoute une nouvelle barre d'outils pour bloc au composant. */
+void gtk_buffer_display_add_block_bar(GtkBufferDisplay *);
+
+
+
#endif /* _GTKEXT_GTKBUFFER_DISPLAY_H */
diff --git a/src/gtkext/gtkdisplaypanel-int.h b/src/gtkext/gtkdisplaypanel-int.h
index 7d64925..62c990e 100644
--- a/src/gtkext/gtkdisplaypanel-int.h
+++ b/src/gtkext/gtkdisplaypanel-int.h
@@ -37,6 +37,10 @@
+/* Amplitude de l'arrondi pour les coins */
+#define BORDER_CORNER_RADIUS 10.0
+
+
/* Indique les dimensions de travail du composant d'affichage. */
typedef void (* compute_requested_size_fc) (GtkDisplayPanel *, gint *, gint *);
diff --git a/src/gtkext/gtkdisplaypanel.c b/src/gtkext/gtkdisplaypanel.c
index e548bf7..010d648 100644
--- a/src/gtkext/gtkdisplaypanel.c
+++ b/src/gtkext/gtkdisplaypanel.c
@@ -33,10 +33,6 @@
-/* Amplitude de l'arrondi pour les coins */
-#define BORDER_CORNER_RADIUS 10.0
-
-
/* Procède à l'initialisation de l'afficheur générique. */
static void gtk_display_panel_class_init(GtkDisplayPanelClass *);
diff --git a/src/gui/core/Makefile.am b/src/gui/core/Makefile.am
index d83d63e..d23dc07 100644
--- a/src/gui/core/Makefile.am
+++ b/src/gui/core/Makefile.am
@@ -1,11 +1,21 @@
+BUILT_SOURCES = resources.h resources.c
+
noinst_LTLIBRARIES = libguicore.la
+RES_FILES = \
+ ../../../pixmaps/palette.png \
+ ../../../pixmaps/collapse.png \
+ ../../../pixmaps/collapse_dark.png \
+ ../../../pixmaps/expand.png \
+ ../../../pixmaps/expand_dark.png
+
libguicore_la_SOURCES = \
core.h core.c \
global.h global.c \
items.h items.c \
panels.h panels.c \
+ resources.h resources.c \
theme.h theme.c
libguicore_la_LDFLAGS = $(LIBGTK_LIBS) $(LIBXML_LIBS)
@@ -21,3 +31,15 @@ AM_CPPFLAGS = $(LIBGTK_CFLAGS) $(LIBXML_CFLAGS)
AM_CFLAGS = $(DEBUG_CFLAGS) $(WARNING_FLAGS) $(COMPLIANCE_FLAGS)
SUBDIRS =
+
+
+resources.c: gresource.xml $(RES_FILES)
+ glib-compile-resources --target=$@ --sourcedir=$(srcdir) --generate-source --c-name gui_core gresource.xml
+
+resources.h: gresource.xml
+ glib-compile-resources --target=$@ --sourcedir=$(srcdir) --generate-header --c-name gui_core gresource.xml
+
+
+CLEANFILES = resources.h resources.c
+
+EXTRA_DIST = gresource.xml $(RES_FILES)
diff --git a/src/gui/core/gresource.xml b/src/gui/core/gresource.xml
new file mode 100644
index 0000000..ad3688c
--- /dev/null
+++ b/src/gui/core/gresource.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<gresources>
+ <gresource prefix="/org/chrysalide/gui/core/images">
+ <file compressed="true" alias="palette.png">../../../pixmaps/palette.png</file>
+ <file compressed="true" alias="collapse.png">../../../pixmaps/collapse.png</file>
+ <file compressed="true" alias="collapse_dark.png">../../../pixmaps/collapse_dark.png</file>
+ <file compressed="true" alias="expand.png">../../../pixmaps/expand.png</file>
+ <file compressed="true" alias="expand_dark.png">../../../pixmaps/expand_dark.png</file>
+ </gresource>
+</gresources>
diff --git a/themes/Adwaita/Makefile.am b/themes/Adwaita/Makefile.am
index 4712702..cf4d6a3 100644
--- a/themes/Adwaita/Makefile.am
+++ b/themes/Adwaita/Makefile.am
@@ -1,8 +1,11 @@
GTK3_CSS = \
+ core.css \
display.css \
icons.css \
portions.css \
+ widgets.css \
+ widgets-dark.css \
clean.png \
redo.png \
undo.png
diff --git a/themes/Adwaita/core.css b/themes/Adwaita/core.css
new file mode 100644
index 0000000..a7760a1
--- /dev/null
+++ b/themes/Adwaita/core.css
@@ -0,0 +1,16 @@
+
+.tinybutton {
+
+ min-width: 12px;
+ min-height: 12px;
+
+ padding: 2px;
+
+}
+
+.tinybutton > image {
+
+ background-position: center center;
+ background-repeat: no-repeat;
+
+}
diff --git a/themes/Adwaita/widgets-dark.css b/themes/Adwaita/widgets-dark.css
new file mode 100644
index 0000000..37d856f
--- /dev/null
+++ b/themes/Adwaita/widgets-dark.css
@@ -0,0 +1,18 @@
+
+.block-palette > image {
+
+ background-image: url('resource:///org/chrysalide/gui/core/images/palette.png');
+
+}
+
+.block-collapse > image {
+
+ background-image: url('resource:///org/chrysalide/gui/core/images/collapse_dark.png');
+
+}
+
+.block-expand > image {
+
+ background-image: url('resource:///org/chrysalide/gui/core/images/expand_dark.png');
+
+}
diff --git a/themes/Adwaita/widgets.css b/themes/Adwaita/widgets.css
new file mode 100644
index 0000000..de84f34
--- /dev/null
+++ b/themes/Adwaita/widgets.css
@@ -0,0 +1,18 @@
+
+.block-palette > image {
+
+ background-image: url('resource:///org/chrysalide/gui/core/images/palette.png');
+
+}
+
+.block-collapse > image {
+
+ background-image: url('resource:///org/chrysalide/gui/core/images/collapse.png');
+
+}
+
+.block-expand > image {
+
+ background-image: url('resource:///org/chrysalide/gui/core/images/expand.png');
+
+}
diff --git a/tools/icons/collapse.py b/tools/icons/collapse.py
new file mode 100644
index 0000000..c3fe508
--- /dev/null
+++ b/tools/icons/collapse.py
@@ -0,0 +1,46 @@
+#!/usr/bin/env python
+
+import cairo
+import sys
+
+
+WIDTH, HEIGHT = 256, 256
+WIDTH, HEIGHT = 12, 12
+
+surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, WIDTH, HEIGHT)
+ctx = cairo.Context(surface)
+
+ctx.scale(WIDTH, HEIGHT)
+
+if len(sys.argv) > 1 and sys.argv[1] == '--dark':
+ ctx.set_source_rgb(1.0, 1.0, 1.0)
+ dark = '_dark'
+else:
+ ctx.set_source_rgb(0.0, 0.0, 0.0)
+ dark = ''
+
+ctx.move_to(0.4, 0.4)
+ctx.line_to(0.4, 0.0)
+ctx.line_to(0.0, 0.4)
+
+ctx.fill()
+
+ctx.move_to(0.6, 0.4)
+ctx.line_to(0.6, 0.0)
+ctx.line_to(1.0, 0.4)
+
+ctx.fill()
+
+ctx.move_to(0.6, 0.6)
+ctx.line_to(1.0, 0.6)
+ctx.line_to(0.6, 1.0)
+
+ctx.fill()
+
+ctx.move_to(0.4, 0.6)
+ctx.line_to(0.4, 1.0)
+ctx.line_to(0.0, 0.6)
+
+ctx.fill()
+
+surface.write_to_png('../../pixmaps/collapse%s.png' % dark)
diff --git a/tools/icons/expand.py b/tools/icons/expand.py
new file mode 100644
index 0000000..854bbc6
--- /dev/null
+++ b/tools/icons/expand.py
@@ -0,0 +1,46 @@
+#!/usr/bin/env python
+
+import cairo
+import sys
+
+
+WIDTH, HEIGHT = 256, 256
+WIDTH, HEIGHT = 12, 12
+
+surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, WIDTH, HEIGHT)
+ctx = cairo.Context(surface)
+
+ctx.scale(WIDTH, HEIGHT)
+
+if len(sys.argv) > 1 and sys.argv[1] == '--dark':
+ ctx.set_source_rgb(1.0, 1.0, 1.0)
+ dark = '_dark'
+else:
+ ctx.set_source_rgb(0.0, 0.0, 0.0)
+ dark = ''
+
+ctx.move_to(0.0, 0.0)
+ctx.line_to(0.4, 0.0)
+ctx.line_to(0.0, 0.4)
+
+ctx.fill()
+
+ctx.move_to(1.0, 0.0)
+ctx.line_to(0.6, 0.0)
+ctx.line_to(1.0, 0.4)
+
+ctx.fill()
+
+ctx.move_to(1.0, 1.0)
+ctx.line_to(1.0, 0.6)
+ctx.line_to(0.6, 1.0)
+
+ctx.fill()
+
+ctx.move_to(0.0, 1.0)
+ctx.line_to(0.4, 1.0)
+ctx.line_to(0.0, 0.6)
+
+ctx.fill()
+
+surface.write_to_png('../../pixmaps/expand%s.png' % dark)
diff --git a/tools/icons/gen.sh b/tools/icons/gen.sh
new file mode 100755
index 0000000..a09bacb
--- /dev/null
+++ b/tools/icons/gen.sh
@@ -0,0 +1,9 @@
+#!/bin/sh
+
+python3 ./palette.py
+
+python3 ./expand.py
+python3 ./expand.py --dark
+
+python3 ./collapse.py
+python3 ./collapse.py --dark
diff --git a/tools/icons/palette.py b/tools/icons/palette.py
new file mode 100644
index 0000000..4227964
--- /dev/null
+++ b/tools/icons/palette.py
@@ -0,0 +1,53 @@
+#!/usr/bin/env python
+
+import math
+import cairo
+
+
+WIDTH, HEIGHT = 256, 256
+WIDTH, HEIGHT = 12, 12
+
+surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, WIDTH, HEIGHT)
+ctx = cairo.Context(surface)
+
+ctx.scale(WIDTH, HEIGHT)
+
+ctx.arc(0.5, 0.5, 0.40, 0, 2 * math.pi)
+ctx.set_source_rgb(1.0, 1.0, 1.0)
+ctx.fill()
+
+colors = [
+ 'fefe33',
+ 'fabc02',
+ 'fb9902',
+ 'fd5308',
+ 'fe2712',
+ 'a7194b',
+ '8601af',
+ '3d01a4',
+ '0247fe',
+ '0391ce',
+ '66b032',
+ 'd0ea2b'
+]
+
+angle = (2 * math.pi) / len(colors)
+
+for i in range(len(colors)):
+
+ hval = int(colors[i], 16)
+
+ red = ((hval & 0xff0000) >> 16) / 256
+ green = ((hval & 0x00ff00) >> 8) / 256
+ blue = ((hval & 0x0000ff) >> 0) / 256
+
+ start = angle / 2 - i * angle
+
+ ctx.arc(0.5, 0.5, 0.5, start - angle, start)
+
+ ctx.arc_negative(0.5, 0.5, 0.15, start, start - angle)
+
+ ctx.set_source_rgb(red, green, blue)
+ ctx.fill()
+
+surface.write_to_png('../../pixmaps/palette.png')