summaryrefslogtreecommitdiff
path: root/dggui
diff options
context:
space:
mode:
Diffstat (limited to 'dggui')
-rw-r--r--dggui/Makefile.am184
-rw-r--r--dggui/button.cc88
-rw-r--r--dggui/button.h64
-rw-r--r--dggui/button_base.cc116
-rw-r--r--dggui/button_base.h78
-rw-r--r--dggui/canvas.h44
-rw-r--r--dggui/checkbox.cc64
-rw-r--r--dggui/checkbox.h49
-rw-r--r--dggui/colour.cc82
-rw-r--r--dggui/colour.h61
-rw-r--r--dggui/combobox.cc234
-rw-r--r--dggui/combobox.h76
-rw-r--r--dggui/dialog.cc54
-rw-r--r--dggui/dialog.h63
-rw-r--r--dggui/drawable.h52
-rw-r--r--dggui/eventhandler.cc297
-rw-r--r--dggui/eventhandler.h78
-rw-r--r--dggui/font.cc136
-rw-r--r--dggui/font.h64
-rw-r--r--dggui/frame.cc146
-rw-r--r--dggui/frame.h115
-rw-r--r--dggui/guievent.h212
-rw-r--r--dggui/helpbutton.cc75
-rw-r--r--dggui/helpbutton.h58
-rw-r--r--dggui/image.cc216
-rw-r--r--dggui/image.h75
-rw-r--r--dggui/imagecache.cc103
-rw-r--r--dggui/imagecache.h72
-rw-r--r--dggui/knob.cc255
-rw-r--r--dggui/knob.h88
-rw-r--r--dggui/label.cc96
-rw-r--r--dggui/label.h69
-rw-r--r--dggui/layout.cc386
-rw-r--r--dggui/layout.h195
-rw-r--r--dggui/led.cc97
-rw-r--r--dggui/led.h54
-rw-r--r--dggui/lineedit.cc285
-rw-r--r--dggui/lineedit.h87
-rw-r--r--dggui/listbox.cc105
-rw-r--r--dggui/listbox.h74
-rw-r--r--dggui/listboxbasic.cc362
-rw-r--r--dggui/listboxbasic.h94
-rw-r--r--dggui/listboxthin.cc101
-rw-r--r--dggui/listboxthin.h75
m---------dggui/lodepng0
-rw-r--r--dggui/nativewindow.h100
-rw-r--r--dggui/nativewindow_cocoa.h78
-rw-r--r--dggui/nativewindow_cocoa.mm832
-rw-r--r--dggui/nativewindow_pugl.cc382
-rw-r--r--dggui/nativewindow_pugl.h83
-rw-r--r--dggui/nativewindow_win32.cc584
-rw-r--r--dggui/nativewindow_win32.h78
-rw-r--r--dggui/nativewindow_x11.cc715
-rw-r--r--dggui/nativewindow_x11.h99
-rw-r--r--dggui/painter.cc644
-rw-r--r--dggui/painter.h103
-rw-r--r--dggui/pixelbuffer.cc369
-rw-r--r--dggui/pixelbuffer.h101
-rw-r--r--dggui/powerbutton.cc88
-rw-r--r--dggui/powerbutton.h58
-rw-r--r--dggui/progressbar.cc104
-rw-r--r--dggui/progressbar.h89
-rw-r--r--dggui/rc_data.cc29
-rw-r--r--dggui/rcgentool.cc192
-rw-r--r--dggui/resource.cc194
-rw-r--r--dggui/resource.h50
-rw-r--r--dggui/resource_data.h33
-rw-r--r--dggui/scrollbar.cc214
-rw-r--r--dggui/scrollbar.h74
-rw-r--r--dggui/slider.cc217
-rw-r--r--dggui/slider.h153
-rw-r--r--dggui/stackedwidget.cc151
-rw-r--r--dggui/stackedwidget.h81
-rw-r--r--dggui/tabbutton.cc132
-rw-r--r--dggui/tabbutton.h85
-rw-r--r--dggui/tabwidget.cc214
-rw-r--r--dggui/tabwidget.h84
-rw-r--r--dggui/textedit.cc201
-rw-r--r--dggui/textedit.h92
-rw-r--r--dggui/texture.cc72
-rw-r--r--dggui/texture.h64
-rw-r--r--dggui/texturedbox.cc147
-rw-r--r--dggui/texturedbox.h119
-rw-r--r--dggui/toggle.cc110
-rw-r--r--dggui/toggle.h70
-rw-r--r--dggui/tooltip.cc200
-rw-r--r--dggui/tooltip.h78
-rw-r--r--dggui/uitranslation.cc51
-rw-r--r--dggui/uitranslation.h41
-rw-r--r--dggui/utf8.cc361
-rw-r--r--dggui/utf8.h47
-rw-r--r--dggui/verticalline.cc51
-rw-r--r--dggui/verticalline.h47
-rw-r--r--dggui/widget.cc297
-rw-r--r--dggui/widget.h139
-rw-r--r--dggui/window.cc266
-rw-r--r--dggui/window.h119
97 files changed, 14361 insertions, 0 deletions
diff --git a/dggui/Makefile.am b/dggui/Makefile.am
new file mode 100644
index 0000000..e55fe12
--- /dev/null
+++ b/dggui/Makefile.am
@@ -0,0 +1,184 @@
+noinst_PROGRAMS = rcgen
+noinst_LTLIBRARIES = libdggui.la
+
+libdggui_la_CPPFLAGS = \
+ $(DEBUG_FLAGS) \
+ -I$(top_srcdir) \
+ $(GUI_CPPFLAGS) \
+ -I$(top_srcdir)/hugin \
+ -I$(top_srcdir)/src \
+ -DWITH_HUG_MUTEX $(PTHREAD_CFLAGS) \
+ -DLODEPNG_NO_COMPILE_ENCODER \
+ -DLODEPNG_NO_COMPILE_DISK \
+ -DLODEPNG_NO_COMPILE_ANCILLARY_CHUNKS \
+ -DLODEPNG_NO_COMPILE_ERROR_TEXT \
+ -DLODEPNG_NO_COMPILE_CPP
+
+libdggui_la_CFLAGS =
+
+libdggui_la_LIBTOOLFLAGS=--tag=CC
+
+libdggui_la_LIBADD = \
+ $(GUI_LIBS) $(PTHREAD_LIBS)
+
+# If you add a file here, remember to add it to plugin/Makefile.mingw32.in
+GUI_SRC = \
+ button.cc \
+ button_base.cc \
+ checkbox.cc \
+ colour.cc \
+ combobox.cc \
+ dialog.cc \
+ eventhandler.cc \
+ font.cc \
+ frame.cc \
+ helpbutton.cc \
+ image.cc \
+ imagecache.cc \
+ knob.cc \
+ label.cc \
+ layout.cc \
+ led.cc \
+ lineedit.cc \
+ listbox.cc \
+ listboxbasic.cc \
+ listboxthin.cc \
+ painter.cc \
+ pixelbuffer.cc \
+ powerbutton.cc \
+ progressbar.cc \
+ rc_data.cc \
+ resource.cc \
+ scrollbar.cc \
+ slider.cc \
+ stackedwidget.cc \
+ tabbutton.cc \
+ tabwidget.cc \
+ textedit.cc \
+ texture.cc \
+ texturedbox.cc \
+ toggle.cc \
+ tooltip.cc \
+ uitranslation.cc \
+ utf8.cc \
+ verticalline.cc \
+ widget.cc \
+ window.cc
+
+GUI_HDR = \
+ button.h \
+ button_base.h \
+ canvas.h \
+ checkbox.h \
+ colour.h \
+ combobox.h \
+ dialog.h \
+ drawable.h \
+ eventhandler.h \
+ font.h \
+ frame.h \
+ guievent.h \
+ helpbutton.h \
+ image.h \
+ imagecache.h \
+ knob.h \
+ label.h \
+ layout.h \
+ led.h \
+ lineedit.h \
+ listbox.h \
+ listboxbasic.h \
+ listboxthin.h \
+ nativewindow.h \
+ nativewindow_cocoa.h \
+ nativewindow_cocoa.mm \
+ nativewindow_pugl.h \
+ nativewindow_pugl.cc \
+ nativewindow_win32.h \
+ nativewindow_win32.cc \
+ nativewindow_x11.h \
+ nativewindow_x11.cc \
+ painter.h \
+ pixelbuffer.h \
+ powerbutton.h \
+ progressbar.h \
+ resource.h \
+ resource_data.h \
+ scrollbar.h \
+ slider.h \
+ stackedwidget.h \
+ tabbutton.h \
+ tabwidget.h \
+ textedit.h \
+ texture.h \
+ texturedbox.h \
+ toggle.h \
+ tooltip.h \
+ uitranslation.h \
+ utf8.h \
+ verticalline.h \
+ widget.h \
+ window.h
+
+libdggui_la_SOURCES = \
+ $(GUI_SRC) \
+ lodepng/lodepng.cpp
+
+nodist_libdggui_la_SOURCES =
+
+if ENABLE_X11
+nodist_libdggui_la_SOURCES += \
+ nativewindow_x11.cc
+endif
+
+if ENABLE_WIN32
+nodist_libdggui_la_SOURCES += \
+ nativewindow_win32.cc
+endif
+
+if ENABLE_COCOA
+nodist_libdggui_la_SOURCES += \
+ nativewindow_cocoa.mm
+
+libdggui_la_OBJCXXFLAGS = \
+ -fblocks
+endif
+
+if ENABLE_PUGL_X11
+nodist_libdggui_la_SOURCES += \
+ nativewindow_pugl.cc \
+ $(top_srcdir)/pugl/pugl/pugl_x11.c
+
+libdggui_la_CPPFLAGS += \
+ -I$(top_srcdir)/pugl
+
+libdggui_la_CFLAGS += \
+ -std=c99
+endif
+
+if ENABLE_PUGL_WIN32
+nodist_libdggui_la_SOURCES += \
+ nativewindow_pugl.cc \
+ $(top_srcdir)/pugl/pugl/pugl_win.cpp
+
+libdggui_la_CPPFLAGS += \
+ -I$(top_srcdir)/pugl
+endif
+
+if ENABLE_PUGL_COCOA
+nodist_libdggui_la_SOURCES += \
+ nativewindow_pugl.cc \
+ $(top_srcdir)/pugl/pugl/pugl_osx.m
+
+libdggui_la_CPPFLAGS += \
+ -I$(top_srcdir)/pugl
+endif
+
+rcgen_LDFLAGS = -static
+rcgen_CXXFLAGS = -I$(top_srcdir)/getoptpp
+rcgen_SOURCES = \
+ rcgentool.cc
+
+EXTRA_DIST = \
+ $(RES) \
+ $(GUI_HDR)
diff --git a/dggui/button.cc b/dggui/button.cc
new file mode 100644
index 0000000..30bda82
--- /dev/null
+++ b/dggui/button.cc
@@ -0,0 +1,88 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/***************************************************************************
+ * button.cc
+ *
+ * Sun Oct 9 13:01:56 CEST 2011
+ * Copyright 2011 Bent Bisballe Nyeng
+ * deva@aasimon.org
+ ****************************************************************************/
+
+/*
+ * This file is part of DrumGizmo.
+ *
+ * DrumGizmo is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * DrumGizmo is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with DrumGizmo; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+#include "button.h"
+
+#include "painter.h"
+
+#include <hugin.hpp>
+#include <stdio.h>
+
+namespace GUI
+{
+
+Button::Button(Widget* parent)
+ : ButtonBase(parent)
+{
+}
+
+Button::~Button()
+{
+}
+
+void Button::repaintEvent(RepaintEvent* repaintEvent)
+{
+ Painter p(*this);
+ p.clear();
+
+ int padTop = 3;
+ int padLeft = 0;
+ int padTextTop = 3;
+
+ int w = width();
+ int h = height();
+ if(w == 0 || h == 0)
+ {
+ return;
+ }
+
+ if (enabled) {
+ switch(draw_state)
+ {
+ case State::Up:
+ box_up.setSize(w - padLeft, h - padTop);
+ p.drawImage(padLeft, padTop, box_up);
+ break;
+
+ case State::Down:
+ box_down.setSize(w - padLeft, h - padTop);
+ p.drawImage(padLeft, padTop, box_down);
+ break;
+ }
+ }
+ else {
+ box_grey.setSize(w - padLeft, h - padTop);
+ p.drawImage(padLeft, padTop, box_grey);
+
+ p.setColour(Colour(0.55));
+ }
+
+ auto x = padLeft + (width() - font.textWidth(text)) / 2;
+ auto y = padTop + padTextTop + font.textHeight(text);
+ p.drawText(x, y, font, text, enabled);
+}
+
+} // GUI::
diff --git a/dggui/button.h b/dggui/button.h
new file mode 100644
index 0000000..52f22e2
--- /dev/null
+++ b/dggui/button.h
@@ -0,0 +1,64 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/***************************************************************************
+ * button.h
+ *
+ * Sun Oct 9 13:01:56 CEST 2011
+ * Copyright 2011 Bent Bisballe Nyeng
+ * deva@aasimon.org
+ ****************************************************************************/
+
+/*
+ * This file is part of DrumGizmo.
+ *
+ * DrumGizmo is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * DrumGizmo is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with DrumGizmo; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+#pragma once
+
+#include "button_base.h"
+#include "font.h"
+#include "texturedbox.h"
+
+namespace GUI {
+
+class Button
+ : public ButtonBase {
+public:
+ Button(Widget* parent);
+ virtual ~Button();
+
+protected:
+ // From Widget:
+ virtual void repaintEvent(RepaintEvent* e) override;
+
+private:
+ TexturedBox box_up{getImageCache(), ":resources/pushbutton.png",
+ 0, 0, // atlas offset (x, y)
+ 7, 1, 7, // dx1, dx2, dx3
+ 6, 12, 9}; // dy1, dy2, dy3
+
+ TexturedBox box_down{getImageCache(), ":resources/pushbutton.png",
+ 15, 0, // atlas offset (x, y)
+ 7, 1, 7, // dx1, dx2, dx3
+ 6, 12, 9}; // dy1, dy2, dy3
+
+ TexturedBox box_grey{getImageCache(), ":resources/pushbutton.png",
+ 30, 0, // atlas offset (x, y)
+ 7, 1, 7, // dx1, dx2, dx3
+ 6, 12, 9}; // dy1, dy2, dy3
+
+ Font font{":resources/fontemboss.png"};
+};
+
+} // GUI::
diff --git a/dggui/button_base.cc b/dggui/button_base.cc
new file mode 100644
index 0000000..bf441f5
--- /dev/null
+++ b/dggui/button_base.cc
@@ -0,0 +1,116 @@
+/* -*- Mode: c++ -*- */
+/***************************************************************************
+ * button_base.cc
+ *
+ * Sat Apr 15 21:45:30 CEST 2017
+ * Copyright 2017 André Nusser
+ * andre.nusser@googlemail.com
+ ****************************************************************************/
+
+/*
+ * This file is part of DrumGizmo.
+ *
+ * DrumGizmo is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * DrumGizmo is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with DrumGizmo; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+#include "button_base.h"
+
+namespace GUI {
+
+ButtonBase::ButtonBase(Widget *parent)
+ : Widget(parent)
+ , draw_state(State::Up)
+ , button_state(State::Up)
+{
+}
+
+ButtonBase::~ButtonBase()
+{
+}
+
+void ButtonBase::buttonEvent(ButtonEvent* buttonEvent)
+{
+ // Ignore everything except left clicks.
+ if(!enabled || buttonEvent->button != MouseButton::left)
+ {
+ return;
+ }
+
+ if(buttonEvent->direction == Direction::down)
+ {
+ draw_state = State::Down;
+ button_state = State::Down;
+ in_button = true;
+ redraw();
+ }
+
+ if(buttonEvent->direction == Direction::up)
+ {
+ draw_state = State::Up;
+ button_state = State::Up;
+ redraw();
+ if(in_button)
+ {
+ clicked();
+ clickNotifier();
+ }
+ }
+}
+
+void ButtonBase::setText(const std::string& text)
+{
+ this->text = text;
+ redraw();
+}
+
+void ButtonBase::setEnabled(bool enabled)
+{
+ this->enabled = enabled;
+ redraw();
+}
+
+bool ButtonBase::isEnabled() const
+{
+ return enabled;
+}
+
+void ButtonBase::mouseLeaveEvent()
+{
+ if (!enabled) {
+ return;
+ }
+
+ in_button = false;
+ if(button_state == State::Down)
+ {
+ draw_state = State::Up;
+ redraw();
+ }
+}
+
+void ButtonBase::mouseEnterEvent()
+{
+ if (!enabled) {
+ return;
+ }
+
+ in_button = true;
+ if(button_state == State::Down)
+ {
+ draw_state = State::Down;
+ redraw();
+ }
+}
+
+} // GUI::
diff --git a/dggui/button_base.h b/dggui/button_base.h
new file mode 100644
index 0000000..c872d9b
--- /dev/null
+++ b/dggui/button_base.h
@@ -0,0 +1,78 @@
+/* -*- Mode: c++ -*- */
+/***************************************************************************
+ * button_base.h
+ *
+ * Sat Apr 15 21:45:30 CEST 2017
+ * Copyright 2017 André Nusser
+ * andre.nusser@googlemail.com
+ ****************************************************************************/
+
+/*
+ * This file is part of DrumGizmo.
+ *
+ * DrumGizmo is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * DrumGizmo is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with DrumGizmo; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+#pragma once
+
+#include <string>
+
+#include <notifier.h>
+
+#include "widget.h"
+
+namespace GUI {
+
+class ButtonBase
+ : public Widget
+{
+public:
+ ButtonBase(Widget* parent);
+ virtual ~ButtonBase();
+
+ // From Widget:
+ bool isFocusable() override { return true; }
+ bool catchMouse() override { return true; }
+
+ void setText(const std::string& text);
+
+ void setEnabled(bool enabled);
+ bool isEnabled() const;
+
+ Notifier<> clickNotifier;
+
+protected:
+ virtual void clicked() {}
+
+ // From Widget:
+ virtual void repaintEvent(RepaintEvent* e) override {};
+ virtual void buttonEvent(ButtonEvent* e) override;
+ virtual void mouseLeaveEvent() override;
+ virtual void mouseEnterEvent() override;
+
+ bool enabled{true};
+ bool in_button{false};
+
+ enum class State {
+ Up,
+ Down
+ };
+
+ std::string text;
+
+ State draw_state{State::Up};
+ State button_state{State::Up};
+};
+
+} // GUI::
diff --git a/dggui/canvas.h b/dggui/canvas.h
new file mode 100644
index 0000000..d6e8f99
--- /dev/null
+++ b/dggui/canvas.h
@@ -0,0 +1,44 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/***************************************************************************
+ * canvas.h
+ *
+ * Sun Sep 4 13:03:51 CEST 2016
+ * Copyright 2016 Bent Bisballe Nyeng
+ * deva@aasimon.org
+ ****************************************************************************/
+
+/*
+ * This file is part of DrumGizmo.
+ *
+ * DrumGizmo is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * DrumGizmo is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with DrumGizmo; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+#pragma once
+
+#include "pixelbuffer.h"
+
+namespace GUI
+{
+
+//! Abstract class that can be used by the Painter to draw on.
+class Canvas
+{
+public:
+ virtual ~Canvas() = default;
+
+ //! @returns a reference to the pixel buffer.
+ virtual PixelBufferAlpha& getPixelBuffer() = 0;
+};
+
+} // GUI::
diff --git a/dggui/checkbox.cc b/dggui/checkbox.cc
new file mode 100644
index 0000000..f3601bd
--- /dev/null
+++ b/dggui/checkbox.cc
@@ -0,0 +1,64 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/***************************************************************************
+ * checkbox.cc
+ *
+ * Sat Nov 26 15:07:44 CET 2011
+ * Copyright 2011 Bent Bisballe Nyeng
+ * deva@aasimon.org
+ ****************************************************************************/
+
+/*
+ * This file is part of DrumGizmo.
+ *
+ * DrumGizmo is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * DrumGizmo is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with DrumGizmo; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+#include "checkbox.h"
+
+#include "painter.h"
+
+namespace GUI
+{
+
+CheckBox::CheckBox(Widget* parent)
+ : Toggle(parent)
+ , bg_on(getImageCache(), ":resources/switch_back_on.png")
+ , bg_off(getImageCache(), ":resources/switch_back_off.png")
+ , knob(getImageCache(), ":resources/switch_front.png")
+{
+}
+
+void CheckBox::repaintEvent(RepaintEvent* repaintEvent)
+{
+ Painter p(*this);
+ p.clear();
+ p.drawImage(0, (knob.height() - bg_on.height()) / 2, state ? bg_on : bg_off);
+
+ if(clicked)
+ {
+ p.drawImage((bg_on.width() - knob.width()) / 2 + 1, 0, knob);
+ return;
+ }
+
+ if(state)
+ {
+ p.drawImage(bg_on.width() - 40 + 2, 0, knob);
+ }
+ else
+ {
+ p.drawImage(0, 0, knob);
+ }
+}
+
+} // GUI::
diff --git a/dggui/checkbox.h b/dggui/checkbox.h
new file mode 100644
index 0000000..6627304
--- /dev/null
+++ b/dggui/checkbox.h
@@ -0,0 +1,49 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/***************************************************************************
+ * checkbox.h
+ *
+ * Sat Nov 26 15:07:44 CET 2011
+ * Copyright 2011 Bent Bisballe Nyeng
+ * deva@aasimon.org
+ ****************************************************************************/
+
+/*
+ * This file is part of DrumGizmo.
+ *
+ * DrumGizmo is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * DrumGizmo is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with DrumGizmo; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+#pragma once
+
+#include "toggle.h"
+#include "texture.h"
+
+namespace GUI {
+
+class CheckBox : public Toggle {
+public:
+ CheckBox(Widget *parent);
+ virtual ~CheckBox() = default;
+
+protected:
+ // From Widget:
+ virtual void repaintEvent(RepaintEvent* repaintEvent) override;
+
+private:
+ Texture bg_on;
+ Texture bg_off;
+ Texture knob;
+};
+
+} // GUI::
diff --git a/dggui/colour.cc b/dggui/colour.cc
new file mode 100644
index 0000000..7fd649c
--- /dev/null
+++ b/dggui/colour.cc
@@ -0,0 +1,82 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/***************************************************************************
+ * colour.cc
+ *
+ * Fri Oct 14 09:38:28 CEST 2011
+ * Copyright 2011 Bent Bisballe Nyeng
+ * deva@aasimon.org
+ ****************************************************************************/
+
+/*
+ * This file is part of DrumGizmo.
+ *
+ * DrumGizmo is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * DrumGizmo is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with DrumGizmo; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+#include "colour.h"
+
+#include <cstring>
+
+namespace GUI
+{
+
+Colour::Colour()
+{
+}
+
+Colour::Colour(float grey, float a)
+ : pixel({{(std::uint8_t)(grey * 255),
+ (std::uint8_t)(grey * 255),
+ (std::uint8_t)(grey * 255),
+ (std::uint8_t)(a * 255)}})
+{
+}
+
+Colour::Colour(float r, float g, float b, float a)
+ : pixel({{(std::uint8_t)(r * 255),
+ (std::uint8_t)(g * 255),
+ (std::uint8_t)(b * 255),
+ (std::uint8_t)(a * 255)}})
+{
+}
+
+Colour::Colour(std::uint8_t r, std::uint8_t g, std::uint8_t b, std::uint8_t a)
+ : pixel({{r, g, b, a}})
+{
+}
+
+Colour::Colour(const Colour& other)
+ : pixel(other.pixel)
+{
+}
+
+Colour& Colour::operator=(const Colour& other)
+{
+ pixel = other.pixel;
+ return *this;
+}
+
+bool Colour::operator==(const Colour& other) const
+{
+ return pixel[0] == other.pixel[0] &&
+ pixel[1] == other.pixel[1] &&
+ pixel[2] == other.pixel[2];
+}
+
+bool Colour::operator!=(const Colour& other) const
+{
+ return !(*this == other);
+}
+
+} // GUI::
diff --git a/dggui/colour.h b/dggui/colour.h
new file mode 100644
index 0000000..0bc8659
--- /dev/null
+++ b/dggui/colour.h
@@ -0,0 +1,61 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/***************************************************************************
+ * colour.h
+ *
+ * Fri Oct 14 09:38:28 CEST 2011
+ * Copyright 2011 Bent Bisballe Nyeng
+ * deva@aasimon.org
+ ****************************************************************************/
+
+/*
+ * This file is part of DrumGizmo.
+ *
+ * DrumGizmo is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * DrumGizmo is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with DrumGizmo; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+#pragma once
+
+#include <array>
+#include <cstdint>
+
+namespace GUI
+{
+
+class Colour
+{
+public:
+ Colour();
+ Colour(float grey, float alpha = 1.0f);
+ Colour(float red, float green, float blue, float alpha = 1.0f);
+ Colour(std::uint8_t r, std::uint8_t g, std::uint8_t b, std::uint8_t a);
+ Colour(const Colour& other);
+
+ Colour& operator=(const Colour& other);
+
+ bool operator==(const Colour& other) const;
+ bool operator!=(const Colour& other) const;
+
+ inline std::uint8_t red() const { return pixel[0]; }
+ inline std::uint8_t green() const { return pixel[1]; }
+ inline std::uint8_t blue() const { return pixel[2]; }
+ inline std::uint8_t alpha() const { return pixel[3]; }
+
+ std::uint8_t* data() { return pixel.data(); }
+ const std::uint8_t* data() const { return pixel.data(); }
+
+private:
+ std::array<std::uint8_t, 4> pixel{{255, 255, 255, 255}};
+};
+
+} // GUI::
diff --git a/dggui/combobox.cc b/dggui/combobox.cc
new file mode 100644
index 0000000..aa2058e
--- /dev/null
+++ b/dggui/combobox.cc
@@ -0,0 +1,234 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/***************************************************************************
+ * combobox.cc
+ *
+ * Sun Mar 10 19:04:50 CET 2013
+ * Copyright 2013 Bent Bisballe Nyeng
+ * deva@aasimon.org
+ ****************************************************************************/
+
+/*
+ * This file is part of DrumGizmo.
+ *
+ * DrumGizmo is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * DrumGizmo is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with DrumGizmo; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+#include "combobox.h"
+
+#include "painter.h"
+#include "font.h"
+
+#include <stdio.h>
+
+#define BORDER 10
+
+namespace GUI
+{
+
+void ComboBox::listboxSelectHandler()
+{
+ ButtonEvent buttonEvent;
+ buttonEvent.direction = Direction::down;
+ this->buttonEvent(&buttonEvent);
+}
+
+ComboBox::ComboBox(Widget* parent)
+ : Widget(parent)
+ , listbox(parent)
+{
+ CONNECT(&listbox, selectionNotifier, this, &ComboBox::listboxSelectHandler);
+ CONNECT(&listbox, clickNotifier, this, &ComboBox::listboxSelectHandler);
+
+ listbox.hide();
+}
+
+ComboBox::~ComboBox()
+{
+}
+
+void ComboBox::addItem(std::string name, std::string value)
+{
+ listbox.addItem(name, value);
+}
+
+void ComboBox::clear()
+{
+ listbox.clear();
+ redraw();
+}
+
+bool ComboBox::selectItem(int index)
+{
+ listbox.selectItem(index);
+ redraw();
+ return true;
+}
+
+std::string ComboBox::selectedName()
+{
+ return listbox.selectedName();
+}
+
+std::string ComboBox::selectedValue()
+{
+ return listbox.selectedValue();
+}
+
+static void drawArrow(Painter &p, int x, int y, int w, int h)
+{
+ p.drawLine(x, y, x+(w/2), y+h);
+ p.drawLine(x+(w/2), y+h, x+w, y);
+
+ y++;
+ p.drawLine(x, y, x+(w/2), y+h);
+ p.drawLine(x+(w/2), y+h, x+w, y);
+}
+
+void ComboBox::repaintEvent(RepaintEvent* repaintEvent)
+{
+ Painter p(*this);
+
+ std::string _text = selectedName();
+
+ int w = width();
+ int h = height();
+ if(w == 0 || h == 0)
+ {
+ return;
+ }
+
+ box.setSize(w, h);
+ p.drawImage(0, 0, box);
+
+ p.setColour(Colour(183.0f/255.0f, 219.0f/255.0f, 255.0/255.0f, 1.0f));
+ p.drawText(BORDER - 4 + 3, height()/2+5 + 1 + 1, font, _text);
+
+ // p.setColour(Colour(1, 1, 1));
+ // p.drawText(BORDER - 4, (height()+font.textHeight()) / 2 + 1, font, _text);
+
+ //int n = height() / 2;
+
+ // p.drawLine(width() - n - 6, 1 + 6, width() - 1 - 6, 1 + 6);
+ {
+ int w = 10;
+ int h = 6;
+ drawArrow(p, width() - 6 - 4 - w, (height() - h) / 2, w, h);
+ p.drawLine(width() - 6 - 4 - w - 4, 7,
+ width() - 6 - 4 - w - 4, height() - 8);
+ }
+}
+
+void ComboBox::scrollEvent(ScrollEvent* scrollEvent)
+{
+ /*
+ scroll_offset += e->delta;
+ if(scroll_offset < 0)
+ {
+ scroll_offset = 0;
+ }
+ if(scroll_offset > (items.size() - 1))
+ {
+ scroll_offset = (items.size() - 1);
+ }
+ redraw();
+ */
+}
+
+void ComboBox::keyEvent(KeyEvent* keyEvent)
+{
+ if(keyEvent->direction != Direction::up)
+ {
+ return;
+ }
+
+ /*
+ switch(keyEvent->keycode) {
+ case Key::up:
+ {
+ selected--;
+ if(selected < 0)
+ {
+ selected = 0;
+ }
+ if(selected < scroll_offset)
+ {
+ scroll_offset = selected;
+ if(scroll_offset < 0)
+ {
+ scroll_offset = 0;
+ }
+ }
+ }
+ break;
+ case Key::down:
+ {
+ // Number of items that can be displayed at a time.
+ int numitems = height() / (font.textHeight() + padding);
+
+ selected++;
+ if(selected > (items.size() - 1))
+ {
+ selected = (items.size() - 1);
+ }
+ if(selected > (scroll_offset + numitems - 1))
+ {
+ scroll_offset = selected - numitems + 1;
+ if(scroll_offset > (items.size() - 1))
+ {
+ scroll_offset = (items.size() - 1);
+ }
+ }
+ }
+ break;
+ case Key::home:
+ selected = 0;
+ break;
+ case Key::end:
+ selected = items.size() - 1;
+ break;
+ default:
+ break;
+ }
+
+ redraw();
+ */
+}
+
+void ComboBox::buttonEvent(ButtonEvent* buttonEvent)
+{
+ // Ignore everything except left clicks.
+ if(buttonEvent->button != MouseButton::left)
+ {
+ return;
+ }
+
+ if(buttonEvent->direction != Direction::down)
+ {
+ return;
+ }
+
+ if(!listbox.visible())
+ {
+ listbox.resize(width() - 10, 100);
+ listbox.move(x() + 5, y() + height() - 7);
+ }
+ else
+ {
+ valueChangedNotifier(listbox.selectedName(), listbox.selectedValue());
+ }
+
+ listbox.setVisible(!listbox.visible());
+}
+
+} // GUI::
diff --git a/dggui/combobox.h b/dggui/combobox.h
new file mode 100644
index 0000000..778d54c
--- /dev/null
+++ b/dggui/combobox.h
@@ -0,0 +1,76 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/***************************************************************************
+ * combobox.h
+ *
+ * Sun Mar 10 19:04:50 CET 2013
+ * Copyright 2013 Bent Bisballe Nyeng
+ * deva@aasimon.org
+ ****************************************************************************/
+
+/*
+ * This file is part of DrumGizmo.
+ *
+ * DrumGizmo is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * DrumGizmo is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with DrumGizmo; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+#pragma once
+
+#include <string.h>
+#include <vector>
+
+#include "widget.h"
+#include "font.h"
+#include "listboxthin.h"
+#include "painter.h"
+#include "texturedbox.h"
+
+namespace GUI
+{
+
+class ComboBox
+ : public Widget
+{
+public:
+ ComboBox(Widget* parent);
+ virtual ~ComboBox();
+
+ void addItem(std::string name, std::string value);
+
+ void clear();
+ bool selectItem(int index);
+ std::string selectedName();
+ std::string selectedValue();
+
+ // From Widget:
+ bool isFocusable() override { return true; }
+ virtual void repaintEvent(RepaintEvent* repaintEvent) override;
+ virtual void buttonEvent(ButtonEvent* buttonEvent) override;
+ virtual void scrollEvent(ScrollEvent* scrollEvent) override;
+ virtual void keyEvent(KeyEvent* keyEvent) override;
+
+ Notifier<std::string, std::string> valueChangedNotifier;
+
+private:
+ TexturedBox box{getImageCache(), ":resources/widget.png",
+ 0, 0, // atlas offset (x, y)
+ 7, 1, 7, // dx1, dx2, dx3
+ 7, 63, 7}; // dy1, dy2, dy3
+
+ void listboxSelectHandler();
+
+ Font font;
+ ListBoxThin listbox;
+};
+
+} // GUI::
diff --git a/dggui/dialog.cc b/dggui/dialog.cc
new file mode 100644
index 0000000..9ba579d
--- /dev/null
+++ b/dggui/dialog.cc
@@ -0,0 +1,54 @@
+/* -*- Mode: c++ -*- */
+/***************************************************************************
+ * dialog.cc
+ *
+ * Sun Apr 16 10:31:04 CEST 2017
+ * Copyright 2017 Bent Bisballe Nyeng
+ * deva@aasimon.org
+ ****************************************************************************/
+
+/*
+ * This file is part of DrumGizmo.
+ *
+ * DrumGizmo is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * DrumGizmo is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with DrumGizmo; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+#include "dialog.h"
+
+namespace GUI
+{
+
+Dialog::Dialog(Widget* parent, bool modal)
+ : parent(parent)
+{
+ parent->window()->eventHandler()->registerDialog(this);
+ setModal(modal);
+}
+
+Dialog::~Dialog()
+{
+ parent->window()->eventHandler()->unregisterDialog(this);
+}
+
+void Dialog::setModal(bool modal)
+{
+ is_modal = modal;
+}
+
+bool Dialog::isModal() const
+{
+ return is_modal;
+}
+
+} // GUI::
diff --git a/dggui/dialog.h b/dggui/dialog.h
new file mode 100644
index 0000000..1b0c6da
--- /dev/null
+++ b/dggui/dialog.h
@@ -0,0 +1,63 @@
+/* -*- Mode: c++ -*- */
+/***************************************************************************
+ * dialog.h
+ *
+ * Sun Apr 16 10:31:04 CEST 2017
+ * Copyright 2017 Bent Bisballe Nyeng
+ * deva@aasimon.org
+ ****************************************************************************/
+
+/*
+ * This file is part of DrumGizmo.
+ *
+ * DrumGizmo is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * DrumGizmo is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with DrumGizmo; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+#pragma once
+
+#include "window.h"
+
+namespace GUI
+{
+
+//! This class is used the base window for pop-up dialogs, such as a file
+//! browser.
+class Dialog
+ : public Window
+{
+public:
+ //! - The dialog is placed near the parent window.
+ //! - The parent window event handler will call the dialog event handler
+ //! - While the dialog is visible, all mouse click and keyboard events
+ //! are ignored by the parent event handler.
+ //! - The Dialog registers itself in the parent event handler when contructed
+ //! and removes itself when destructed.
+ //! - The parent event handler will delete all registered Dialogs when itself
+ //! deleted.
+ Dialog(Widget* parent, bool modal = false);
+
+ ~Dialog();
+
+ //! Change modality.
+ void setModal(bool modal);
+
+ //! Get current modality state.
+ bool isModal() const;
+
+private:
+ bool is_modal{false};
+ Widget* parent{nullptr};
+};
+
+} // GUI::
diff --git a/dggui/drawable.h b/dggui/drawable.h
new file mode 100644
index 0000000..95492d6
--- /dev/null
+++ b/dggui/drawable.h
@@ -0,0 +1,52 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/***************************************************************************
+ * drawable.h
+ *
+ * Sat Jun 4 21:39:38 CEST 2016
+ * Copyright 2016 Bent Bisballe Nyeng
+ * deva@aasimon.org
+ ****************************************************************************/
+
+/*
+ * This file is part of DrumGizmo.
+ *
+ * DrumGizmo is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * DrumGizmo is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with DrumGizmo; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+#pragma once
+
+#include <cstdlib>
+#include <cstdint>
+
+namespace GUI
+{
+
+class Colour;
+
+class Drawable
+{
+public:
+ virtual ~Drawable() = default;
+
+ virtual std::size_t width() const = 0;
+ virtual std::size_t height() const = 0;
+
+ virtual const Colour& getPixel(std::size_t x, std::size_t y) const = 0;
+ virtual const std::uint8_t* line(std::size_t y,
+ std::size_t x_offset = 0) const = 0;
+
+ virtual bool hasAlpha() const = 0;
+};
+
+} // GUI::
diff --git a/dggui/eventhandler.cc b/dggui/eventhandler.cc
new file mode 100644
index 0000000..fd333b8
--- /dev/null
+++ b/dggui/eventhandler.cc
@@ -0,0 +1,297 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/***************************************************************************
+ * eventhandler.cc
+ *
+ * Sun Oct 9 18:58:29 CEST 2011
+ * Copyright 2011 Bent Bisballe Nyeng
+ * deva@aasimon.org
+ ****************************************************************************/
+
+/*
+ * This file is part of DrumGizmo.
+ *
+ * DrumGizmo is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * DrumGizmo is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with DrumGizmo; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+#include "eventhandler.h"
+
+#include "window.h"
+#include "painter.h"
+#include "dialog.h"
+
+namespace GUI
+{
+
+EventHandler::EventHandler(NativeWindow& nativeWindow, Window& window)
+ : window(window)
+ , nativeWindow(nativeWindow)
+ , lastWasDoubleClick(false)
+{}
+
+bool EventHandler::hasEvent()
+{
+ return !events.empty();
+}
+
+bool EventHandler::queryNextEventType(EventType type)
+{
+ return !events.empty() &&
+ (events.front()->type() == type);
+}
+
+std::shared_ptr<Event> EventHandler::getNextEvent()
+{
+ if(events.empty())
+ {
+ return nullptr;
+ }
+
+ auto event = events.front();
+ events.pop_front();
+ return event;
+}
+
+void EventHandler::processEvents()
+{
+ bool block_interaction{false};
+ for(auto dialog : dialogs)
+ {
+ // Check if the dialog nativewindow (not the contained widget) is visible
+ if(dialog->native->visible())
+ {
+ block_interaction |= dialog->isModal();
+ dialog->eventHandler()->processEvents();
+ }
+ }
+
+ events = nativeWindow.getEvents();
+
+ while(hasEvent())
+ {
+ auto event = getNextEvent();
+
+ if(event == nullptr)
+ {
+ continue;
+ }
+
+ switch(event->type()) {
+ case EventType::repaint:
+ break;
+
+ case EventType::move:
+ {
+ auto moveEvent = static_cast<MoveEvent*>(event.get());
+ window.moved(moveEvent->x, moveEvent->y);
+ }
+ break;
+
+ case EventType::resize:
+ {
+ auto resizeEvent = static_cast<ResizeEvent*>(event.get());
+ if((resizeEvent->width != window.width()) ||
+ (resizeEvent->height != window.height()))
+ {
+ window.resized(resizeEvent->width, resizeEvent->height);
+ }
+ }
+ break;
+
+ case EventType::mouseMove:
+ {
+ // Skip all consecutive mouse move events and handle only the last one.
+ while(queryNextEventType(EventType::mouseMove))
+ {
+ event = getNextEvent();
+ }
+
+ auto moveEvent = static_cast<MouseMoveEvent*>(event.get());
+
+ auto widget = window.find(moveEvent->x, moveEvent->y);
+ auto oldwidget = window.mouseFocus();
+ if(widget != oldwidget)
+ {
+ // Send focus leave to oldwidget
+ if(oldwidget)
+ {
+ oldwidget->mouseLeaveEvent();
+ }
+
+ // Send focus enter to widget
+ if(widget)
+ {
+ widget->mouseEnterEvent();
+ }
+
+ window.setMouseFocus(widget);
+ }
+
+ if(window.buttonDownFocus())
+ {
+ auto widget = window.buttonDownFocus();
+ moveEvent->x -= widget->translateToWindowX();
+ moveEvent->y -= widget->translateToWindowY();
+
+ window.buttonDownFocus()->mouseMoveEvent(moveEvent);
+ break;
+ }
+
+ if(widget)
+ {
+ moveEvent->x -= widget->translateToWindowX();
+ moveEvent->y -= widget->translateToWindowY();
+ widget->mouseMoveEvent(moveEvent);
+ }
+ }
+ break;
+
+ case EventType::button:
+ {
+ if(block_interaction)
+ {
+ continue;
+ }
+
+ auto buttonEvent = static_cast<ButtonEvent*>(event.get());
+ if(lastWasDoubleClick && (buttonEvent->direction == Direction::down))
+ {
+ lastWasDoubleClick = false;
+ continue;
+ }
+
+ lastWasDoubleClick = buttonEvent->doubleClick;
+
+ auto widget = window.find(buttonEvent->x, buttonEvent->y);
+
+ if(window.buttonDownFocus())
+ {
+ if(buttonEvent->direction == Direction::up)
+ {
+ auto widget = window.buttonDownFocus();
+ buttonEvent->x -= widget->translateToWindowX();
+ buttonEvent->y -= widget->translateToWindowY();
+
+ widget->buttonEvent(buttonEvent);
+ window.setButtonDownFocus(nullptr);
+ break;
+ }
+ }
+
+ if(widget)
+ {
+ buttonEvent->x -= widget->translateToWindowX();
+ buttonEvent->y -= widget->translateToWindowY();
+
+ widget->buttonEvent(buttonEvent);
+
+ if((buttonEvent->direction == Direction::down) &&
+ widget->catchMouse())
+ {
+ window.setButtonDownFocus(widget);
+ }
+
+ if(widget->isFocusable())
+ {
+ window.setKeyboardFocus(widget);
+ }
+ }
+ }
+ break;
+
+ case EventType::scroll:
+ {
+ if(block_interaction)
+ {
+ continue;
+ }
+
+ auto scrollEvent = static_cast<ScrollEvent*>(event.get());
+
+ auto widget = window.find(scrollEvent->x, scrollEvent->y);
+ if(widget)
+ {
+ scrollEvent->x -= widget->translateToWindowX();
+ scrollEvent->y -= widget->translateToWindowY();
+
+ widget->scrollEvent(scrollEvent);
+ }
+ }
+ break;
+
+ case EventType::key:
+ {
+ if(block_interaction)
+ {
+ continue;
+ }
+
+ // TODO: Filter out multiple arrow events.
+
+ auto keyEvent = static_cast<KeyEvent*>(event.get());
+ if(window.keyboardFocus())
+ {
+ window.keyboardFocus()->keyEvent(keyEvent);
+ }
+ }
+ break;
+
+ case EventType::close:
+ if(block_interaction)
+ {
+ continue;
+ }
+
+ closeNotifier();
+ break;
+
+ case EventType::mouseEnter:
+ {
+ auto enterEvent = static_cast<MouseEnterEvent*>(event.get());
+ auto widget = window.find(enterEvent->x, enterEvent->y);
+ if(widget)
+ {
+ widget->mouseEnterEvent();
+ }
+ }
+ break;
+
+ case EventType::mouseLeave:
+ {
+ auto widget = window.mouseFocus();
+ if(widget)
+ {
+ widget->mouseLeaveEvent();
+ }
+ }
+ break;
+ }
+ }
+
+ // Probe window and children to redraw as needed.
+ // NOTE: This method will invoke native->redraw() if a redraw is needed.
+ window.updateBuffer();
+}
+
+void EventHandler::registerDialog(Dialog* dialog)
+{
+ dialogs.push_back(dialog);
+}
+
+void EventHandler::unregisterDialog(Dialog* dialog)
+{
+ dialogs.remove(dialog);
+}
+
+
+} // GUI::
diff --git a/dggui/eventhandler.h b/dggui/eventhandler.h
new file mode 100644
index 0000000..8d6f492
--- /dev/null
+++ b/dggui/eventhandler.h
@@ -0,0 +1,78 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/***************************************************************************
+ * eventhandler.h
+ *
+ * Sun Oct 9 18:58:29 CEST 2011
+ * Copyright 2011 Bent Bisballe Nyeng
+ * deva@aasimon.org
+ ****************************************************************************/
+
+/*
+ * This file is part of DrumGizmo.
+ *
+ * DrumGizmo is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * DrumGizmo is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with DrumGizmo; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+#pragma once
+
+#include <notifier.h>
+#include <memory>
+#include <queue>
+#include <list>
+
+#include "guievent.h"
+#include "nativewindow.h"
+
+namespace GUI
+{
+
+class Window;
+class Dialog;
+
+class EventHandler
+{
+public:
+ EventHandler(NativeWindow& nativeWindow, Window& window);
+
+ //! Process all events currently in the event queue.
+ void processEvents();
+
+ //! Query if any events are currently in the event queue.
+ bool hasEvent();
+
+ //! Query if the topmost event in the event queue is of type.
+ bool queryNextEventType(EventType type);
+
+ //! Get a single event from the event queue.
+ //! \return A pointer to the event or nullptr if there are none.
+ std::shared_ptr<Event> getNextEvent();
+
+ void registerDialog(Dialog* dialog);
+ void unregisterDialog(Dialog* dialog);
+
+ Notifier<> closeNotifier;
+
+private:
+ Window& window;
+ NativeWindow& nativeWindow;
+
+ // Used to ignore mouse button release after a double click.
+ bool lastWasDoubleClick;
+
+ EventQueue events;
+
+ std::list<Dialog*> dialogs;
+};
+
+} // GUI::
diff --git a/dggui/font.cc b/dggui/font.cc
new file mode 100644
index 0000000..0500e81
--- /dev/null
+++ b/dggui/font.cc
@@ -0,0 +1,136 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/***************************************************************************
+ * font.cc
+ *
+ * Sat Nov 12 11:13:41 CET 2011
+ * Copyright 2011 Bent Bisballe Nyeng
+ * deva@aasimon.org
+ ****************************************************************************/
+
+/*
+ * This file is part of DrumGizmo.
+ *
+ * DrumGizmo is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * DrumGizmo is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with DrumGizmo; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+#include "font.h"
+
+#include <cassert>
+
+namespace GUI
+{
+
+Font::Font(const std::string& fontfile)
+ : img_font(fontfile)
+{
+ std::size_t px = 0;
+ std::size_t c;
+
+ for(c = 0; c < characters.size() && px < img_font.width(); ++c)
+ {
+ auto& character = characters[c];
+ character.offset = px + 1;
+
+ if(c > 0)
+ {
+ assert(character.offset >= characters[c - 1].offset);
+ characters[c - 1].width = character.offset - characters[c - 1].offset;
+ if(characters[c].offset != characters[c - 1].offset)
+ {
+ --characters[c - 1].width;
+ }
+ }
+
+ ++px;
+
+ while(px < img_font.width())
+ {
+ auto& pixel = img_font.getPixel(px, 0);
+
+ // Find next purple pixel in top row:
+ if((pixel.red() == 255) && (pixel.green() == 0) &&
+ (pixel.blue() == 255) && (pixel.alpha() == 255))
+ {
+ break;
+ }
+
+ ++px;
+ }
+
+ characters[c] = character;
+ }
+
+ --c;
+
+ assert(characters[c].offset >= characters[c - 1].offset);
+ characters[c - 1].width = characters[c].offset - characters[c - 1].offset;
+ if(characters[c].offset != characters[c - 1].offset)
+ {
+ --characters[c - 1].width;
+ }
+}
+
+size_t Font::textWidth(const std::string& text) const
+{
+ size_t len = 0;
+
+ for(unsigned char cha : text)
+ {
+ auto& character = characters[cha];
+ len += character.width + spacing + character.post_bias;
+ }
+
+ return len;
+}
+
+size_t Font::textHeight(const std::string& text) const
+{
+ return img_font.height();
+}
+
+void Font::setLetterSpacing(int letterSpacing)
+{
+ spacing = letterSpacing;
+}
+
+int Font::letterSpacing() const
+{
+ return spacing;
+}
+
+PixelBufferAlpha *Font::render(const std::string& text) const
+{
+ PixelBufferAlpha *pb =
+ new PixelBufferAlpha(textWidth(text), textHeight(text));
+
+ int x_offset = 0;
+ for(std::size_t i = 0; i < text.length(); ++i)
+ {
+ unsigned char cha = text[i];
+ auto& character = characters.at(cha);
+ for(size_t x = 0; x < character.width; ++x)
+ {
+ for(size_t y = 0; y < img_font.height(); ++y)
+ {
+ auto& c = img_font.getPixel(x + character.offset, y);
+ pb->setPixel(x + x_offset + character.pre_bias, y, c);
+ }
+ }
+ x_offset += character.width + spacing + character.post_bias;
+ }
+
+ return pb;
+}
+
+} // GUI::
diff --git a/dggui/font.h b/dggui/font.h
new file mode 100644
index 0000000..2e3f87a
--- /dev/null
+++ b/dggui/font.h
@@ -0,0 +1,64 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/***************************************************************************
+ * font.h
+ *
+ * Sat Nov 12 11:13:41 CET 2011
+ * Copyright 2011 Bent Bisballe Nyeng
+ * deva@aasimon.org
+ ****************************************************************************/
+
+/*
+ * This file is part of DrumGizmo.
+ *
+ * DrumGizmo is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * DrumGizmo is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with DrumGizmo; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+#pragma once
+
+#include <string>
+#include <array>
+
+#include "pixelbuffer.h"
+#include "image.h"
+
+namespace GUI {
+
+class Font {
+public:
+ Font(const std::string& fontfile = ":resources/font.png");
+
+ size_t textWidth(const std::string& text) const;
+ size_t textHeight(const std::string& text = "") const;
+
+ void setLetterSpacing(int letterSpacing);
+ int letterSpacing() const;
+
+ PixelBufferAlpha *render(const std::string& text) const;
+
+private:
+ Image img_font;
+
+ class Character {
+ public:
+ std::size_t offset{0};
+ std::size_t width{0};
+ int pre_bias{0};
+ int post_bias{0};
+ };
+
+ std::array<Character, 256> characters;
+ int spacing{1};
+};
+
+} // GUI::
diff --git a/dggui/frame.cc b/dggui/frame.cc
new file mode 100644
index 0000000..d375b6d
--- /dev/null
+++ b/dggui/frame.cc
@@ -0,0 +1,146 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/***************************************************************************
+ * frame.cc
+ *
+ * Tue Feb 7 21:07:56 CET 2017
+ * Copyright 2017 André Nusser
+ * andre.nusser@googlemail.com
+ ****************************************************************************/
+
+/*
+ * This file is part of DrumGizmo.
+ *
+ * DrumGizmo is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * DrumGizmo is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with DrumGizmo; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+#include "frame.h"
+
+#include "painter.h"
+
+namespace GUI
+{
+
+FrameWidget::FrameWidget(Widget* parent, bool has_switch, bool has_help_text)
+ : Widget(parent)
+ , is_switched_on(!has_switch)
+ , bar_height(24)
+{
+ if(has_switch)
+ {
+ // We only have to set this once as nothing happens on a resize
+ power_button.move(4, 4);
+ power_button.resize(16, 16);
+
+ power_button.setChecked(is_switched_on);
+ CONNECT(&power_button, stateChangedNotifier, this,
+ &FrameWidget::powerButtonStateChanged);
+ }
+ power_button.setVisible(has_switch);
+
+ if(has_help_text)
+ {
+ // We only have to set this once as nothing happens on a resize
+ help_button.resize(16, 16);
+ help_button.move(width() - 4 - 16, 4);
+ help_button.setText("?");
+ }
+ help_button.setVisible(has_help_text);
+
+ CONNECT(this, sizeChangeNotifier, this, &FrameWidget::sizeChanged);
+}
+
+void FrameWidget::repaintEvent(RepaintEvent* repaintEvent)
+{
+ Painter p(*this);
+
+ int center_x = width() / 2;
+ auto title_buf = title.c_str();
+
+ // draw the dark grey box
+ p.setColour(enabled ? grey_box_colour : grey_box_colour_disabled);
+ p.drawFilledRectangle(1, 1, width() - 2, bar_height);
+
+ // frame
+ p.setColour(frame_colour_top);
+ p.drawLine(0, 0, width() - 1, 0);
+ p.setColour(frame_colour_bottom);
+ p.drawLine(0, height() - 1, width() - 1, height() - 1);
+ p.setColour(frame_colour_side);
+ p.drawLine(0, 0, 0, height() - 1);
+ p.drawLine(width() - 1, 0, width() - 1, height() - 1);
+
+ // background
+ p.setColour(background_colour);
+ p.drawFilledRectangle(1, bar_height, width() - 2, height() - 2);
+
+ // draw the label
+ p.setColour(enabled ? label_colour : label_colour_disabled);
+ p.drawText(center_x - label_width, bar_height - 4, font, title_buf);
+ power_button.setEnabled(enabled);
+}
+
+void FrameWidget::powerButtonStateChanged(bool new_state)
+{
+ is_switched_on = new_state;
+ onSwitchChangeNotifier(is_switched_on);
+}
+
+void FrameWidget::setTitle(std::string const& title)
+{
+ this->title = title;
+ label_width = font.textWidth(title.c_str()) / 2 + 1;
+}
+
+void FrameWidget::setHelpText(const std::string& help_text)
+{
+ help_button.setHelpText(help_text);
+}
+
+void FrameWidget::setContent(Widget* content)
+{
+ this->content = content;
+ content->reparent(this);
+}
+
+void FrameWidget::setOnSwitch(bool on)
+{
+ is_switched_on = on;
+ power_button.setChecked(is_switched_on);
+}
+
+void FrameWidget::setEnabled(bool enabled)
+{
+ this->enabled = enabled;
+ onEnabledChanged(enabled);
+
+ redraw();
+}
+
+void FrameWidget::sizeChanged(int width, int height)
+{
+ if(content)
+ {
+ content_start_x = content_margin;
+ content_start_y = bar_height + content_margin;
+ content_width = std::max((int)width - 2 * content_margin, 0);
+ content_height = std::max((int)height - (bar_height + 2 * content_margin), 0);
+
+ content->move(content_start_x, content_start_y);
+ content->resize(content_width, content_height);
+ }
+
+ help_button.move(width - 4 - 16, help_button.y());
+}
+
+} // GUI::
diff --git a/dggui/frame.h b/dggui/frame.h
new file mode 100644
index 0000000..db26ea9
--- /dev/null
+++ b/dggui/frame.h
@@ -0,0 +1,115 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/***************************************************************************
+ * frame.h
+ *
+ * Tue Feb 7 21:07:56 CET 2017
+ * Copyright 2017 André Nusser
+ * andre.nusser@googlemail.com
+ ****************************************************************************/
+
+/*
+ * This file is part of DrumGizmo.
+ *
+ * DrumGizmo is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * DrumGizmo is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with DrumGizmo; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+#pragma once
+
+#include <notifier.h>
+
+#include "font.h"
+#include "powerbutton.h"
+#include "helpbutton.h"
+#include "widget.h"
+
+namespace GUI
+{
+
+class FrameWidget
+ : public Widget
+{
+public:
+ FrameWidget(Widget* parent, bool has_switch = false, bool has_help_text = false);
+ virtual ~FrameWidget() = default;
+
+ // From Widget:
+ virtual bool isFocusable() override { return false; }
+ virtual bool catchMouse() override { return false; }
+
+ bool isSwitchedOn() { return is_switched_on; }
+
+ void setTitle(const std::string& title);
+ void setHelpText(const std::string& help_text);
+ void setContent(Widget* content);
+
+ void setOnSwitch(bool on);
+ void setEnabled(bool enabled);
+
+ Notifier<bool> onSwitchChangeNotifier; // (bool on)
+ Notifier<bool> onEnabledChanged; // (bool enabled)
+
+protected:
+ // From Widget:
+ virtual void repaintEvent(RepaintEvent* repaintEvent) override;
+
+ //! Callback for Widget::sizeChangeNotifier
+ void sizeChanged(int width, int height);
+
+ bool enabled = true;
+
+private:
+ //
+ // upper bar
+ //
+
+ // label
+ Font font;
+ std::string title;
+ GUI::Colour label_colour{0.1};
+ GUI::Colour label_colour_disabled{0.5};
+ std::size_t label_width;
+
+ // switch
+ bool is_switched_on;
+ PowerButton power_button{this};
+ HelpButton help_button{this};
+
+ void powerButtonStateChanged(bool clicked);
+
+ // grey box
+ int bar_height;
+ GUI::Colour grey_box_colour{0.7};
+ GUI::Colour grey_box_colour_disabled{0.7};
+ GUI::Colour background_colour{0.85, 0.8};
+
+ //
+ // content
+ //
+
+ // content frame
+ GUI::Colour frame_colour_top{0.95};
+ GUI::Colour frame_colour_bottom{0.4};
+ GUI::Colour frame_colour_side{0.6};
+
+ // content box
+ Widget* content{nullptr};
+ int content_margin{12};
+
+ int content_start_x;
+ int content_start_y;
+ int content_width;
+ int content_height;
+};
+
+} // GUI::
diff --git a/dggui/guievent.h b/dggui/guievent.h
new file mode 100644
index 0000000..4ad0798
--- /dev/null
+++ b/dggui/guievent.h
@@ -0,0 +1,212 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/***************************************************************************
+ * event.h
+ *
+ * Sun Oct 9 16:11:47 CEST 2011
+ * Copyright 2011 Bent Bisballe Nyeng
+ * deva@aasimon.org
+ ****************************************************************************/
+
+/*
+ * This file is part of DrumGizmo.
+ *
+ * DrumGizmo is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * DrumGizmo is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with DrumGizmo; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+#pragma once
+
+#include <string>
+#include <list>
+#include <memory>
+
+namespace GUI
+{
+
+enum class EventType
+{
+ mouseMove,
+ repaint,
+ button,
+ scroll,
+ key,
+ close,
+ resize,
+ move,
+ mouseEnter,
+ mouseLeave,
+};
+
+class Event
+{
+public:
+ virtual ~Event() {}
+
+ virtual EventType type() = 0;
+};
+
+class MouseMoveEvent
+ : public Event
+{
+public:
+ EventType type() { return EventType::mouseMove; }
+
+ int x;
+ int y;
+};
+
+
+enum class Direction
+{
+ up,
+ down,
+};
+
+enum class MouseButton
+{
+ right,
+ middle,
+ left,
+};
+
+class ButtonEvent
+ : public Event
+{
+public:
+ EventType type() { return EventType::button; }
+
+ int x;
+ int y;
+
+ Direction direction;
+ MouseButton button;
+
+ bool doubleClick;
+};
+
+class ScrollEvent
+ : public Event
+{
+public:
+ EventType type() { return EventType::scroll; }
+
+ int x;
+ int y;
+
+ float delta;
+};
+
+class RepaintEvent
+ : public Event
+{
+public:
+ EventType type() { return EventType::repaint; }
+
+ int x;
+ int y;
+ size_t width;
+ size_t height;
+};
+
+enum class Key
+{
+ unknown,
+ left,
+ right,
+ up,
+ down,
+ deleteKey,
+ backspace,
+ home,
+ end,
+ pageDown,
+ pageUp,
+ enter,
+ character, //!< The actual character is stored in KeyEvent::text
+};
+
+class KeyEvent
+ : public Event
+{
+public:
+ EventType type() { return EventType::key; }
+
+ Direction direction;
+
+ Key keycode;
+ std::string text;
+};
+
+class CloseEvent
+ : public Event
+{
+public:
+ EventType type() { return EventType::close; }
+};
+
+class ResizeEvent
+ : public Event
+{
+public:
+ EventType type() { return EventType::resize; }
+
+ size_t width;
+ size_t height;
+};
+
+class MoveEvent
+ : public Event
+{
+public:
+ EventType type() { return EventType::move; }
+
+ int x;
+ int y;
+};
+
+class MouseEnterEvent
+ : public Event
+{
+public:
+ EventType type() { return EventType::mouseEnter; }
+
+ int x;
+ int y;
+};
+
+class MouseLeaveEvent
+ : public Event
+{
+public:
+ EventType type() { return EventType::mouseLeave; }
+
+ int x;
+ int y;
+};
+
+using EventQueue = std::list<std::shared_ptr<Event>>;
+
+struct Rect
+{
+ std::size_t x1;
+ std::size_t y1;
+ std::size_t x2;
+ std::size_t y2;
+
+ bool empty() const
+ {
+ return x1 == x2 && y1 == y2;
+ }
+};
+
+} // GUI::
diff --git a/dggui/helpbutton.cc b/dggui/helpbutton.cc
new file mode 100644
index 0000000..fa061a6
--- /dev/null
+++ b/dggui/helpbutton.cc
@@ -0,0 +1,75 @@
+/* -*- Mode: c++ -*- */
+/***************************************************************************
+ * helpbutton.cc
+ *
+ * Wed May 8 17:10:08 CEST 2019
+ * Copyright 2019 Bent Bisballe Nyeng
+ * deva@aasimon.org
+ ****************************************************************************/
+
+/*
+ * This file is part of DrumGizmo.
+ *
+ * DrumGizmo is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * DrumGizmo is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with DrumGizmo; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+#include "helpbutton.h"
+
+#include "painter.h"
+
+#include <iostream>
+
+namespace GUI
+{
+
+HelpButton::HelpButton(Widget* parent)
+ : ButtonBase(parent)
+ , tip(this)
+{
+ CONNECT(this, clickNotifier, this, &HelpButton::showHelpText);
+ tip.hide();
+}
+
+void HelpButton::setHelpText(const std::string& help_text)
+{
+ tip.setText(help_text);
+}
+
+void HelpButton::repaintEvent(RepaintEvent* repaintEvent)
+{
+ Painter p(*this);
+
+ bool state = true;
+
+ // enabled and on
+ if(state)
+ {
+ if(button_state == ButtonBase::State::Down)
+ {
+ p.drawImage(0, 0, pushed);
+ }
+ else
+ {
+ p.drawImage(0, 0, normal);
+ }
+ return;
+ }
+}
+
+void HelpButton::showHelpText()
+{
+ tip.show();
+}
+
+} // GUI::
diff --git a/dggui/helpbutton.h b/dggui/helpbutton.h
new file mode 100644
index 0000000..6e97eca
--- /dev/null
+++ b/dggui/helpbutton.h
@@ -0,0 +1,58 @@
+/* -*- Mode: c++ -*- */
+/***************************************************************************
+ * helpbutton.h
+ *
+ * Wed May 8 17:10:08 CEST 2019
+ * Copyright 2019 Bent Bisballe Nyeng
+ * deva@aasimon.org
+ ****************************************************************************/
+
+/*
+ * This file is part of DrumGizmo.
+ *
+ * DrumGizmo is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * DrumGizmo is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with DrumGizmo; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+#pragma once
+
+#include "texture.h"
+#include "button_base.h"
+#include "tooltip.h"
+
+namespace GUI
+{
+
+class HelpButton
+ : public ButtonBase
+{
+public:
+ HelpButton(Widget* parent);
+ virtual ~HelpButton() = default;
+
+ void setHelpText(const std::string& help_text);
+
+protected:
+ // From Widget:
+ virtual void repaintEvent(RepaintEvent* repaintEvent) override;
+
+private:
+ void showHelpText();
+
+ Texture normal{getImageCache(), ":resources/help_button.png", 0, 0, 16, 16};
+ Texture pushed{getImageCache(), ":resources/help_button.png", 16, 0, 16, 16};
+
+ Tooltip tip;
+};
+
+} // GUI::
diff --git a/dggui/image.cc b/dggui/image.cc
new file mode 100644
index 0000000..118203e
--- /dev/null
+++ b/dggui/image.cc
@@ -0,0 +1,216 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/***************************************************************************
+ * image.cc
+ *
+ * Sat Mar 16 15:05:09 CET 2013
+ * Copyright 2013 Bent Bisballe Nyeng
+ * deva@aasimon.org
+ ****************************************************************************/
+
+/*
+ * This file is part of DrumGizmo.
+ *
+ * DrumGizmo is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * DrumGizmo is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with DrumGizmo; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+#include "image.h"
+
+#include <cstring>
+#include <cstdint>
+#include <cstdlib>
+#include <cassert>
+
+#include <hugin.hpp>
+
+#include "resource.h"
+#include "lodepng/lodepng.h"
+
+namespace GUI
+{
+
+Image::Image(const char* data, size_t size)
+{
+ load(data, size);
+}
+
+Image::Image(const std::string& filename)
+ : filename(filename)
+{
+ Resource rc(filename);
+ if(!rc.valid())
+ {
+ setError();
+ return;
+ }
+ load(rc.data(), rc.size());
+}
+
+Image::Image(Image&& other)
+ : _width(other._width)
+ , _height(other._height)
+ , image_data(std::move(other.image_data))
+ , image_data_raw(std::move(other.image_data_raw))
+ , filename(other.filename)
+{
+ other._width = 0;
+ other._height = 0;
+}
+
+Image::~Image()
+{
+}
+
+Image& Image::operator=(Image&& other)
+{
+ image_data.clear();
+ image_data = std::move(other.image_data);
+ image_data_raw.clear();
+ image_data_raw = std::move(other.image_data_raw);
+ _width = other._width;
+ _height = other._height;
+ valid = other.valid;
+
+ other._width = 0;
+ other._height = 0;
+ other.valid = false;
+ return *this;
+}
+
+void Image::setError()
+{
+ valid = false;
+ Resource rc(":resources/png_error");
+ if(!rc.valid())
+ {
+ _width = _height = 0u;
+ return;
+ }
+
+ const unsigned char* ptr = (const unsigned char*)rc.data();
+
+ std::uint32_t iw, ih;
+
+ iw = (uint32_t) ptr[0] |
+ (uint32_t) ptr[1] << 8 |
+ (uint32_t) ptr[2] << 16 |
+ (uint32_t) ptr[3] << 24;
+ ptr += sizeof(uint32_t);
+
+ ih = (uint32_t) ptr[0] |
+ (uint32_t) ptr[1] << 8 |
+ (uint32_t) ptr[2] << 16 |
+ (uint32_t) ptr[3] << 24;
+ ptr += sizeof(uint32_t);
+
+ _width = iw;
+ _height = ih;
+
+ image_data.clear();
+ image_data.reserve(_width * _height);
+
+ image_data_raw.clear();
+ image_data_raw.reserve(_width * _height * 4);
+ memcpy(image_data_raw.data(), ptr, _height * _width);
+
+ for(std::size_t y = 0; y < _height; ++y)
+ {
+ for(std::size_t x = 0; x < _width; ++x)
+ {
+ image_data.emplace_back(Colour{ptr[0] / 255.0f, ptr[1] / 255.0f,
+ ptr[2] / 255.0f, ptr[3] / 255.0f});
+ }
+ }
+
+ assert(image_data.size() == (_width * _height));
+}
+
+void Image::load(const char* data, size_t size)
+{
+ has_alpha = false;
+ unsigned int iw{0}, ih{0};
+ std::uint8_t* char_image_data{nullptr};
+ unsigned int res = lodepng_decode32((std::uint8_t**)&char_image_data,
+ &iw, &ih,
+ (const std::uint8_t*)data, size);
+
+ if(res != 0)
+ {
+ ERR(image, "Error in lodepng_decode32: %d while loading '%s'",
+ res, filename.c_str());
+ setError();
+ return;
+ }
+
+ _width = iw;
+ _height = ih;
+
+ image_data.clear();
+ image_data.reserve(_width * _height);
+
+ image_data_raw.clear();
+ image_data_raw.reserve(_width * _height * 4);
+ memcpy(image_data_raw.data(), char_image_data, _height * _width * 4);
+
+ for(std::size_t y = 0; y < _height; ++y)
+ {
+ for(std::size_t x = 0; x < _width; ++x)
+ {
+ std::uint8_t* ptr = &char_image_data[(x + y * _width) * 4];
+ image_data.emplace_back(Colour{ptr[0], ptr[1], ptr[2], ptr[3]});
+ has_alpha |= ptr[3] != 0xff;
+ }
+ }
+
+ assert(image_data.size() == (_width * _height));
+
+ std::free(char_image_data);
+ valid = true;
+}
+
+size_t Image::width() const
+{
+ return _width;
+}
+
+size_t Image::height() const
+{
+ return _height;
+}
+
+const Colour& Image::getPixel(size_t x, size_t y) const
+{
+ if(x > _width || y > _height)
+ {
+ return out_of_range;
+ }
+
+ return image_data[x + y * _width];
+}
+
+const std::uint8_t* Image::line(std::size_t y, std::size_t x_offset) const
+{
+ return image_data_raw.data() + y * _width * 4 + x_offset * 4;
+}
+
+bool Image::hasAlpha() const
+{
+ return has_alpha;
+}
+
+bool Image::isValid() const
+{
+ return valid;
+}
+
+} // GUI::
diff --git a/dggui/image.h b/dggui/image.h
new file mode 100644
index 0000000..d162a75
--- /dev/null
+++ b/dggui/image.h
@@ -0,0 +1,75 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/***************************************************************************
+ * image.h
+ *
+ * Sat Mar 16 15:05:08 CET 2013
+ * Copyright 2013 Bent Bisballe Nyeng
+ * deva@aasimon.org
+ ****************************************************************************/
+
+/*
+ * This file is part of DrumGizmo.
+ *
+ * DrumGizmo is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * DrumGizmo is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with DrumGizmo; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+#pragma once
+
+#include <string>
+#include <vector>
+
+#include "drawable.h"
+#include "colour.h"
+#include "resource.h"
+
+namespace GUI {
+
+class Image
+ : public Drawable
+{
+public:
+ Image(const char* data, size_t size);
+ Image(const std::string& filename);
+ Image(Image&& other);
+ virtual ~Image();
+
+ Image& operator=(Image&& other);
+
+ size_t width() const override;
+ size_t height() const override;
+
+ const Colour& getPixel(size_t x, size_t y) const override;
+ const std::uint8_t* line(std::size_t y,
+ std::size_t x_offset = 0) const override;
+
+ bool hasAlpha() const override;
+
+ bool isValid() const;
+
+protected:
+ void setError();
+ bool valid{false};
+
+ void load(const char* data, size_t size);
+
+ std::size_t _width{0};
+ std::size_t _height{0};
+ std::vector<Colour> image_data;
+ std::vector<std::uint8_t> image_data_raw;
+ Colour out_of_range{0.0f, 0.0f, 0.0f, 0.0f};
+ std::string filename;
+ bool has_alpha{false};
+};
+
+} // GUI::
diff --git a/dggui/imagecache.cc b/dggui/imagecache.cc
new file mode 100644
index 0000000..d3130fe
--- /dev/null
+++ b/dggui/imagecache.cc
@@ -0,0 +1,103 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/***************************************************************************
+ * imagecache.cc
+ *
+ * Thu Jun 2 17:12:05 CEST 2016
+ * Copyright 2016 Bent Bisballe Nyeng
+ * deva@aasimon.org
+ ****************************************************************************/
+
+/*
+ * This file is part of DrumGizmo.
+ *
+ * DrumGizmo is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * DrumGizmo is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with DrumGizmo; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+#include "imagecache.h"
+
+#include <cassert>
+
+#include "image.h"
+
+namespace GUI
+{
+
+ScopedImageBorrower::ScopedImageBorrower(ImageCache& imageCache,
+ const std::string& filename)
+ : imageCache(imageCache)
+ , filename(filename)
+ , image(imageCache.borrow(filename))
+{
+}
+
+ScopedImageBorrower::ScopedImageBorrower(ScopedImageBorrower&& other)
+ : imageCache(other.imageCache)
+ , filename(other.filename)
+ , image(other.image)
+{
+ other.filename.clear();
+}
+
+ScopedImageBorrower::~ScopedImageBorrower()
+{
+ if(!filename.empty())
+ {
+ imageCache.giveBack(filename);
+ }
+}
+
+Image& ScopedImageBorrower::operator*()
+{
+ return image;
+}
+
+Image& ScopedImageBorrower::operator()()
+{
+ return image;
+}
+
+ScopedImageBorrower ImageCache::getImage(const std::string& filename)
+{
+ return ScopedImageBorrower(*this, filename);
+}
+
+Image& ImageCache::borrow(const std::string& filename)
+{
+ auto cacheIterator = imageCache.find(filename);
+ if(cacheIterator == imageCache.end())
+ {
+ Image image(filename);
+ auto insertValue =
+ imageCache.emplace(filename, std::make_pair(0, std::move(image)));
+ cacheIterator = insertValue.first;
+ }
+
+ auto& cacheEntry = cacheIterator->second;
+ ++cacheEntry.first;
+ return cacheEntry.second;
+}
+
+void ImageCache::giveBack(const std::string& filename)
+{
+ auto cacheIterator = imageCache.find(filename);
+ assert(cacheIterator != imageCache.end());
+ auto& cacheEntry = cacheIterator->second;
+ --cacheEntry.first;
+ if(cacheEntry.first == 0)
+ {
+ imageCache.erase(cacheIterator);
+ }
+}
+
+} // GUI::
diff --git a/dggui/imagecache.h b/dggui/imagecache.h
new file mode 100644
index 0000000..d31a844
--- /dev/null
+++ b/dggui/imagecache.h
@@ -0,0 +1,72 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/***************************************************************************
+ * imagecache.h
+ *
+ * Thu Jun 2 17:12:05 CEST 2016
+ * Copyright 2016 Bent Bisballe Nyeng
+ * deva@aasimon.org
+ ****************************************************************************/
+
+/*
+ * This file is part of DrumGizmo.
+ *
+ * DrumGizmo is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * DrumGizmo is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with DrumGizmo; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+#pragma once
+
+#include <string>
+#include <map>
+#include <utility>
+
+namespace GUI
+{
+
+class Image;
+class ImageCache;
+
+class ScopedImageBorrower
+{
+public:
+ ScopedImageBorrower(ImageCache& imageCache, const std::string& filename);
+ ScopedImageBorrower(ScopedImageBorrower&& other);
+ virtual ~ScopedImageBorrower();
+
+ ScopedImageBorrower& operator=(ScopedImageBorrower&& other);
+
+ Image& operator*();
+ Image& operator()();
+
+protected:
+ ImageCache& imageCache;
+ std::string filename;
+ Image& image;
+};
+
+class ImageCache
+{
+public:
+ ScopedImageBorrower getImage(const std::string& filename);
+
+private:
+ friend class ScopedImageBorrower;
+
+ Image& borrow(const std::string& filename);
+ void giveBack(const std::string& filename);
+
+protected:
+ std::map<std::string, std::pair<std::size_t, Image>> imageCache;
+};
+
+} // GUI::
diff --git a/dggui/knob.cc b/dggui/knob.cc
new file mode 100644
index 0000000..25200a7
--- /dev/null
+++ b/dggui/knob.cc
@@ -0,0 +1,255 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/***************************************************************************
+ * knob.cc
+ *
+ * Thu Feb 28 07:37:27 CET 2013
+ * Copyright 2013 Bent Bisballe Nyeng
+ * deva@aasimon.org
+ ****************************************************************************/
+
+/*
+ * This file is part of DrumGizmo.
+ *
+ * DrumGizmo is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * DrumGizmo is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with DrumGizmo; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+#include "knob.h"
+
+#include "painter.h"
+
+#include <hugin.hpp>
+#include <cmath>
+
+namespace
+{
+const double pi = std::atan(1.0) * 4.0;
+}
+
+namespace GUI
+{
+
+Knob::Knob(Widget *parent)
+ : Widget(parent)
+ , img_knob(getImageCache(), ":resources/knob.png")
+{
+ state = up;
+
+ maximum = 1.0;
+ minimum = 0.0;
+
+ current_value = 0.0;
+
+ mouse_offset_x = 0;
+}
+
+void Knob::setValue(float value)
+{
+ value -= minimum;
+ value /= (maximum - minimum);
+ internalSetValue(value);
+}
+
+void Knob::setDefaultValue(float value)
+{
+ default_value = value;
+}
+
+void Knob::setRange(float minimum, float maximum)
+{
+ this->minimum = minimum;
+ this->maximum = maximum;
+ internalSetValue(current_value);
+}
+
+float Knob::value()
+{
+ return current_value * (maximum - minimum) + minimum;
+}
+
+void Knob::showValue(bool show_value)
+{
+ this->show_value = show_value;
+}
+
+void Knob::scrollEvent(ScrollEvent* scrollEvent)
+{
+ float value = (current_value - (scrollEvent->delta / 200.0));
+ internalSetValue(value);
+}
+
+void Knob::mouseMoveEvent(MouseMoveEvent* mouseMoveEvent)
+{
+ if(state == down)
+ {
+ if(mouse_offset_x == (mouseMoveEvent->x + (-1 * mouseMoveEvent->y)))
+ {
+ return;
+ }
+
+ float dval =
+ mouse_offset_x - (mouseMoveEvent->x + (-1 * mouseMoveEvent->y));
+ float value = current_value - (dval / 300.0);
+
+ internalSetValue(value);
+
+ mouse_offset_x = mouseMoveEvent->x + (-1 * mouseMoveEvent->y);
+ }
+}
+
+void Knob::keyEvent(KeyEvent* keyEvent)
+{
+ if(keyEvent->direction != Direction::up)
+ {
+ return;
+ }
+
+ float value = current_value;
+ switch(keyEvent->keycode) {
+ case Key::up:
+ value += 0.01;
+ break;
+ case Key::down:
+ value -= 0.01;
+ break;
+ case Key::right:
+ value += 0.01;
+ break;
+ case Key::left:
+ value -= 0.01;
+ break;
+ case Key::home:
+ value = 0;
+ break;
+ case Key::end:
+ value = 1;
+ break;
+ default:
+ break;
+ }
+
+ internalSetValue(value);
+}
+
+void Knob::buttonEvent(ButtonEvent* buttonEvent)
+{
+ // Ignore everything except left clicks.
+ if(buttonEvent->button != MouseButton::left)
+ {
+ return;
+ }
+
+ if(buttonEvent->doubleClick)
+ {
+ float value = default_value;
+ value -= minimum;
+ value /= (maximum - minimum);
+ internalSetValue(value);
+ return;
+ }
+
+ if(buttonEvent->direction == Direction::down)
+ {
+ state = down;
+ mouse_offset_x = buttonEvent->x + (-1 * buttonEvent->y);
+ return;
+ }
+
+ if(buttonEvent->direction == Direction::up)
+ {
+ state = up;
+ mouse_offset_x = buttonEvent->x + (-1 * buttonEvent->y);
+ clicked();
+ return;
+ }
+}
+
+void Knob::repaintEvent(RepaintEvent* repaintEvent)
+{
+ int diameter = (width()>height()?height():width());
+ int radius = diameter / 2;
+ int center_x = width() / 2;
+ int center_y = height() / 2;
+
+ Painter p(*this);
+ p.clear();
+
+ p.drawImageStretched(0, 0, img_knob, diameter, diameter);
+
+ float range = maximum - minimum;
+
+ if (show_value) {
+ // Show 0, 1 or 2 decimal point depending on the size of the range
+ char buf[64];
+ if(range> 100.0f)
+ {
+ sprintf(buf, "%.0f", current_value * range + minimum);
+ }
+ else if(range > 10.0f)
+ {
+ sprintf(buf, "%.1f", current_value * range + minimum);
+ }
+ else
+ {
+ sprintf(buf, "%.2f", current_value * range + minimum);
+ }
+ p.drawText(center_x - font.textWidth(buf) / 2 + 1,
+ center_y + font.textHeight(buf) / 2 + 1, font, buf);
+ }
+
+ // Make it start from 20% and stop at 80%
+ double padval = current_value * 0.8 + 0.1;
+
+ double from_x = sin((-1 * padval + 1) * 2 * pi) * radius * 0.6;
+ double from_y = cos((-1 * padval + 1) * 2 * pi) * radius * 0.6;
+
+ double to_x = sin((-1 * padval + 1) * 2 * pi) * radius * 0.8;
+ double to_y = cos((-1 * padval + 1) * 2 * pi) * radius * 0.8;
+
+ // Draw "fat" line by drawing 9 lines with moved start/ending points.
+ p.setColour(Colour(1.0f, 0.0f, 0.0f, 1.0f));
+ for(int _x = -1; _x < 2; _x++)
+ {
+ for(int _y = -1; _y < 2; _y++)
+ {
+ p.drawLine(from_x + center_x + _x,
+ from_y + center_y + _y,
+ to_x + center_x + _x,
+ to_y + center_y + _y);
+ }
+ }
+}
+
+void Knob::internalSetValue(float new_value)
+{
+ if(new_value < 0.0)
+ {
+ new_value = 0.0;
+ }
+
+ if(new_value > 1.0)
+ {
+ new_value = 1.0;
+ }
+
+ if(new_value == current_value)
+ {
+ return;
+ }
+
+ current_value = new_value;
+ valueChangedNotifier(value());
+ redraw();
+}
+
+} // GUI::
diff --git a/dggui/knob.h b/dggui/knob.h
new file mode 100644
index 0000000..fc71511
--- /dev/null
+++ b/dggui/knob.h
@@ -0,0 +1,88 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/***************************************************************************
+ * knob.h
+ *
+ * Thu Feb 28 07:37:27 CET 2013
+ * Copyright 2013 Bent Bisballe Nyeng
+ * deva@aasimon.org
+ ****************************************************************************/
+
+/*
+ * This file is part of DrumGizmo.
+ *
+ * DrumGizmo is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * DrumGizmo is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with DrumGizmo; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+#pragma once
+
+#include <notifier.h>
+
+#include "widget.h"
+#include "texture.h"
+#include "font.h"
+
+namespace GUI {
+
+class Knob : public Widget {
+public:
+ Knob(Widget *parent);
+ virtual ~Knob() = default;
+
+ // From Widget:
+ bool catchMouse() override { return true; }
+ bool isFocusable() override { return true; }
+
+ void setValue(float value);
+ void setDefaultValue(float value);
+ void setRange(float minimum, float maximum);
+ float value();
+ void showValue(bool show_value);
+
+ Notifier<float> valueChangedNotifier; // (float newValue)
+
+protected:
+ virtual void clicked() {}
+
+ // From Widget:
+ virtual void repaintEvent(RepaintEvent* repaintEvent) override;
+ virtual void buttonEvent(ButtonEvent* buttonEvent) override;
+ virtual void mouseMoveEvent(MouseMoveEvent* mouseMoveEvent) override;
+ virtual void scrollEvent(ScrollEvent* scrollEvent) override;
+ virtual void keyEvent(KeyEvent* keyEvent) override;
+
+private:
+ //! Sets the internal value and sends out the changed notification.
+ void internalSetValue(float value);
+
+ typedef enum {
+ up,
+ down
+ } state_t;
+
+ state_t state;
+
+ float current_value;
+ float default_value = 0.0;
+ float maximum;
+ float minimum;
+
+ bool show_value{true};
+
+ Texture img_knob;
+
+ int mouse_offset_x;
+ Font font;
+};
+
+} // GUI::
diff --git a/dggui/label.cc b/dggui/label.cc
new file mode 100644
index 0000000..b5239ec
--- /dev/null
+++ b/dggui/label.cc
@@ -0,0 +1,96 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/***************************************************************************
+ * label.cc
+ *
+ * Sun Oct 9 13:02:18 CEST 2011
+ * Copyright 2011 Bent Bisballe Nyeng
+ * deva@aasimon.org
+ ****************************************************************************/
+
+/*
+ * This file is part of DrumGizmo.
+ *
+ * DrumGizmo is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * DrumGizmo is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with DrumGizmo; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+#include "label.h"
+
+#include "painter.h"
+#include "guievent.h"
+
+#include <cpp11fix.h>
+
+namespace GUI {
+
+Label::Label(Widget *parent)
+ : Widget(parent)
+{
+}
+
+void Label::setText(const std::string& text)
+{
+ _text = text;
+ redraw();
+}
+
+void Label::setAlignment(TextAlignment alignment)
+{
+ this->alignment = alignment;
+}
+
+void Label::setColour(Colour colour)
+{
+ this->colour = std::make_unique<Colour>(colour);
+ redraw();
+}
+
+void Label::resetColour()
+{
+ colour.release();
+ redraw();
+}
+
+void Label::resizeToText()
+{
+ resize(font.textWidth(_text) + border, font.textHeight());
+}
+
+void Label::repaintEvent(RepaintEvent* repaintEvent)
+{
+ Painter p(*this);
+ p.clear();
+
+ int offset = 0;
+ switch(alignment) {
+ case TextAlignment::left:
+ offset = border;
+ break;
+ case TextAlignment::center:
+ offset = (width() - font.textWidth(_text)) / 2;
+ break;
+ case TextAlignment::right:
+ offset = width() - font.textWidth(_text) - border;
+ break;
+ }
+
+ if (colour) {
+ p.setColour(*colour);
+ p.drawText(offset, (height() + font.textHeight()) / 2, font, _text);
+ }
+ else {
+ p.drawText(offset, (height() + font.textHeight()) / 2, font, _text, true);
+ }
+}
+
+} // GUI::
diff --git a/dggui/label.h b/dggui/label.h
new file mode 100644
index 0000000..45b4176
--- /dev/null
+++ b/dggui/label.h
@@ -0,0 +1,69 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/***************************************************************************
+ * label.h
+ *
+ * Sun Oct 9 13:02:17 CEST 2011
+ * Copyright 2011 Bent Bisballe Nyeng
+ * deva@aasimon.org
+ ****************************************************************************/
+
+/*
+ * This file is part of DrumGizmo.
+ *
+ * DrumGizmo is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * DrumGizmo is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with DrumGizmo; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+#pragma once
+
+#include "widget.h"
+
+#include "font.h"
+
+#include <string>
+#include <memory>
+
+namespace GUI {
+
+enum class TextAlignment {
+ left,
+ center,
+ right,
+};
+
+class Label : public Widget {
+public:
+ Label(Widget *parent);
+ virtual ~Label() = default;
+
+ void setText(const std::string& text);
+ void setAlignment(TextAlignment alignment);
+ void setColour(Colour colour);
+ void resetColour();
+ void resizeToText();
+
+protected:
+ // From Widget:
+ virtual void repaintEvent(RepaintEvent* repaintEvent) override;
+
+private:
+ std::string _text;
+ Font font{":resources/fontemboss.png"};
+ TextAlignment alignment{TextAlignment::left};
+ int border{0};
+
+ // optional colour
+ std::unique_ptr<Colour> colour;
+};
+
+} // GUI::
diff --git a/dggui/layout.cc b/dggui/layout.cc
new file mode 100644
index 0000000..61e4f77
--- /dev/null
+++ b/dggui/layout.cc
@@ -0,0 +1,386 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/***************************************************************************
+ * layout.cc
+ *
+ * Sat Mar 21 15:12:36 CET 2015
+ * Copyright 2015 Bent Bisballe Nyeng
+ * deva@aasimon.org
+ ****************************************************************************/
+
+/*
+ * This file is part of DrumGizmo.
+ *
+ * DrumGizmo is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * DrumGizmo is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with DrumGizmo; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+#include "layout.h"
+
+#include "widget.h"
+
+#include <algorithm>
+
+namespace GUI
+{
+
+LayoutItem::LayoutItem()
+ : parent(nullptr)
+{
+}
+
+LayoutItem::~LayoutItem()
+{
+ setLayoutParent(nullptr); // Will disconnect from layout if any.
+}
+
+void LayoutItem::setLayoutParent(Layout* p)
+{
+ if(this->parent)
+ {
+ this->parent->removeItem(this);
+ }
+
+ this->parent = p;
+}
+
+Layout::Layout(LayoutItem* parent) : parent(parent)
+{
+ auto widget = dynamic_cast<Widget*>(parent);
+ if(widget)
+ {
+ CONNECT(widget, sizeChangeNotifier, this, &Layout::sizeChanged);
+ }
+}
+
+void Layout::addItem(LayoutItem* item)
+{
+ items.push_back(item);
+ item->setLayoutParent(this);
+ layout();
+}
+
+void Layout::removeItem(LayoutItem* item)
+{
+ auto new_end = std::remove(items.begin(), items.end(), item);
+ items.erase(new_end, items.end());
+
+ layout();
+}
+
+void Layout::sizeChanged(int width, int height)
+{
+ layout();
+}
+
+//
+// BoxLayout
+//
+
+BoxLayout::BoxLayout(LayoutItem* parent) : Layout(parent)
+{
+}
+
+void BoxLayout::setResizeChildren(bool resizeChildren)
+{
+ this->resizeChildren = resizeChildren;
+ layout();
+}
+
+void BoxLayout::setSpacing(size_t spacing)
+{
+ this->spacing = spacing;
+ layout();
+}
+
+//
+// VBoxLayout
+//
+
+VBoxLayout::VBoxLayout(LayoutItem* parent)
+ : BoxLayout(parent)
+ , align(HAlignment::center)
+{
+}
+
+void VBoxLayout::layout()
+{
+ size_t y = 0;
+ size_t w = parent->width();
+ // size_t h = parent->height() / items.size();
+
+ LayoutItemList::iterator i = items.begin();
+ while(i != items.end())
+ {
+ LayoutItem* item = *i;
+
+ if(resizeChildren)
+ {
+ auto num_items = items.size();
+ auto empty_space = (num_items - 1) * spacing;
+ auto available_space = parent->height();
+
+ if(available_space >= empty_space)
+ {
+ auto item_height = (available_space - empty_space) / num_items;
+ item->resize(w, item_height);
+ }
+ else
+ {
+ // TODO: Should this case be handled differently?
+ item->resize(w, 0);
+ }
+ }
+
+ size_t x = 0;
+ switch(align)
+ {
+ case HAlignment::left:
+ x = 0;
+ break;
+ case HAlignment::center:
+ x = (w / 2) - (item->width() / 2);
+ break;
+ case HAlignment::right:
+ x = w - item->width();
+ break;
+ }
+
+ item->move(x, y);
+ y += item->height() + spacing;
+ ++i;
+ }
+}
+
+void VBoxLayout::setHAlignment(HAlignment alignment)
+{
+ align = alignment;
+}
+
+//
+// HBoxLayout
+//
+
+HBoxLayout::HBoxLayout(LayoutItem* parent)
+ : BoxLayout(parent)
+ , align(VAlignment::center)
+{
+}
+
+void HBoxLayout::layout()
+{
+ if(items.empty())
+ {
+ return;
+ }
+
+ // size_t w = parent->width() / items.size();
+ size_t h = parent->height();
+ size_t x = 0;
+
+ LayoutItemList::iterator i = items.begin();
+ while(i != items.end())
+ {
+ LayoutItem* item = *i;
+ if(resizeChildren)
+ {
+ auto num_items = items.size();
+ auto empty_space = (num_items - 1) * spacing;
+ auto available_space = parent->width();
+
+ if(available_space >= empty_space)
+ {
+ auto item_width = (available_space - empty_space) / num_items;
+ item->resize(item_width, h);
+ }
+ else
+ {
+ // TODO: Should this case be handled differently?
+ item->resize(0, h);
+ }
+
+ item->move(x, 0);
+ }
+ else
+ {
+ size_t y = 0;
+ switch(align)
+ {
+ case VAlignment::top:
+ y = 0;
+ break;
+ case VAlignment::center:
+ y = (h / 2) - (item->height() / 2);
+ break;
+ case VAlignment::bottom:
+ y = h - item->height();
+ break;
+ }
+
+ int diff = 0; // w - item->width();
+ item->move(x + diff / 2, y);
+ }
+ x += item->width() + spacing;
+ ++i;
+ }
+}
+
+void HBoxLayout::setVAlignment(VAlignment alignment)
+{
+ align = alignment;
+}
+
+//
+// GridLayout
+//
+
+GridLayout::GridLayout(LayoutItem* parent, std::size_t number_of_columns,
+ std::size_t number_of_rows)
+ : BoxLayout(parent)
+ , number_of_columns(number_of_columns)
+ , number_of_rows(number_of_rows)
+{
+}
+
+void GridLayout::removeItem(LayoutItem* item)
+{
+ // manually remove from grid_ranges as remove_if doesn't work on an
+ // unordered_map.
+ auto it = grid_ranges.begin();
+ while(it != grid_ranges.end())
+ {
+ if(it->first == item)
+ {
+ it = grid_ranges.erase(it);
+ }
+ else
+ {
+ ++it;
+ }
+ }
+
+ Layout::removeItem(item);
+}
+
+void GridLayout::layout()
+{
+ if(grid_ranges.empty())
+ {
+ return;
+ }
+
+ // Calculate cell sizes
+ auto cell_size = calculateCellSize();
+
+ for(auto const& pair : grid_ranges)
+ {
+ auto& item = *pair.first;
+ auto const& range = pair.second;
+
+ moveAndResize(item, range, cell_size);
+ }
+}
+
+void GridLayout::setPosition(LayoutItem* item, GridRange const& range)
+{
+ grid_ranges[item] = range;
+}
+
+int GridLayout::lastUsedRow(int column) const
+{
+ int last_row = -1;
+
+ for (auto const& grid_range : grid_ranges)
+ {
+ auto const& range = grid_range.second;
+ if (column >= range.column_begin && column < range.column_end)
+ {
+ last_row = std::max(last_row, range.row_end - 1);
+ }
+ }
+
+ return last_row;
+}
+
+int GridLayout::lastUsedColumn(int row) const
+{
+ int last_column = -1;
+
+ for (auto const& grid_range : grid_ranges)
+ {
+ auto const& range = grid_range.second;
+ if (row >= range.row_begin && row < range.row_end)
+ {
+ last_column = std::max(last_column, range.column_end - 1);
+ }
+ }
+
+ return last_column;
+
+}
+
+auto GridLayout::calculateCellSize() const -> CellSize
+{
+ auto empty_width = (number_of_columns - 1) * spacing;
+ auto available_width = parent->width();
+ auto empty_height = (number_of_rows - 1) * spacing;
+ auto available_height = parent->height();
+
+ CellSize cell_size;
+ if(available_width > empty_width && available_height > empty_height)
+ {
+ cell_size.width = (available_width - empty_width) / number_of_columns;
+ cell_size.height = (available_height - empty_height) / number_of_rows;
+ }
+ else
+ {
+ cell_size.width = 0;
+ cell_size.height = 0;
+ }
+
+ return cell_size;
+}
+
+void GridLayout::moveAndResize(
+ LayoutItem& item, GridRange const& range, CellSize cell_size) const
+{
+ std::size_t x = range.column_begin * (cell_size.width + spacing);
+ std::size_t y = range.row_begin * (cell_size.height + spacing);
+
+ std::size_t column_count = (range.column_end - range.column_begin);
+ std::size_t row_count = (range.row_end - range.row_begin);
+ std::size_t width = column_count * (cell_size.width + spacing) - spacing;
+ std::size_t height = row_count * (cell_size.height + spacing) - spacing;
+
+ if(resizeChildren)
+ {
+ item.move(x, y);
+
+ if(cell_size.width * cell_size.height != 0)
+ {
+ item.resize(width, height);
+ }
+ else
+ {
+ item.resize(0, 0);
+ }
+ }
+ else
+ {
+ auto x_new = (item.width() > width) ? x : x + (width - item.width()) / 2;
+ auto y_new = (item.height() > height) ? y : y + (height - item.height()) / 2;
+
+ item.move(x_new, y_new);
+ }
+}
+
+} // GUI::
diff --git a/dggui/layout.h b/dggui/layout.h
new file mode 100644
index 0000000..210c86e
--- /dev/null
+++ b/dggui/layout.h
@@ -0,0 +1,195 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/***************************************************************************
+ * layout.h
+ *
+ * Sat Mar 21 15:12:36 CET 2015
+ * Copyright 2015 Bent Bisballe Nyeng
+ * deva@aasimon.org
+ ****************************************************************************/
+
+/*
+ * This file is part of DrumGizmo.
+ *
+ * DrumGizmo is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * DrumGizmo is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with DrumGizmo; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+#pragma once
+
+#include <cstdlib>
+#include <list>
+#include <unordered_map>
+
+#include <notifier.h>
+
+namespace GUI
+{
+
+class Layout;
+
+class LayoutItem
+{
+public:
+ LayoutItem();
+ virtual ~LayoutItem();
+
+ void setLayoutParent(Layout* parent);
+
+ virtual void resize(std::size_t width, std::size_t height) = 0;
+ virtual void move(int x, int y) = 0;
+ virtual int x() const = 0;
+ virtual int y() const = 0;
+ virtual std::size_t width() const = 0;
+ virtual std::size_t height() const = 0;
+
+private:
+ Layout* parent;
+};
+
+//! \brief Abtract Layout class.
+class Layout : public Listener
+{
+public:
+ Layout(LayoutItem* parent);
+ virtual ~Layout()
+ {
+ }
+
+ virtual void addItem(LayoutItem* item);
+ virtual void removeItem(LayoutItem* item);
+
+ //! \brief Reimplement this method to create a new Layout rule.
+ virtual void layout() = 0;
+
+protected:
+ void sizeChanged(int width, int height);
+
+ LayoutItem* parent;
+ typedef std::list<LayoutItem*> LayoutItemList;
+ LayoutItemList items;
+};
+
+//! \brief Abstract box layout
+class BoxLayout : public Layout
+{
+public:
+ BoxLayout(LayoutItem* parent);
+
+ //! \brief Set to false to only move the items, not scale them.
+ void setResizeChildren(bool resize_children);
+
+ void setSpacing(size_t spacing);
+
+ // From Layout:
+ virtual void layout() override = 0;
+
+protected:
+ bool resizeChildren{false};
+ size_t spacing{0};
+};
+
+enum class HAlignment
+{
+ left,
+ center,
+ right,
+};
+
+//! \brief A Layout that lays out its elements vertically.
+class VBoxLayout : public BoxLayout
+{
+public:
+ VBoxLayout(LayoutItem* parent);
+
+ void setHAlignment(HAlignment alignment);
+
+ // From BoxLayout:
+ virtual void layout() override;
+
+protected:
+ HAlignment align;
+};
+
+enum class VAlignment
+{
+ top,
+ center,
+ bottom,
+};
+
+//! \brief A Layout that lays out its elements vertically.
+class HBoxLayout : public BoxLayout
+{
+public:
+ HBoxLayout(LayoutItem* parent);
+
+ void setVAlignment(VAlignment alignment);
+
+ // From BoxLayout:
+ virtual void layout() override;
+
+protected:
+ VAlignment align;
+};
+
+//! \brief A Layout class which places the items in a regular grid. An item can
+//! span multiple rows/columns.
+class GridLayout : public BoxLayout
+{
+public:
+ // The range is open, i.e. end is one past the last one.
+ struct GridRange
+ {
+ int column_begin;
+ int column_end;
+ int row_begin;
+ int row_end;
+ };
+
+ GridLayout(LayoutItem* parent, std::size_t number_of_columns,
+ std::size_t number_of_rows);
+
+ virtual ~GridLayout()
+ {
+ }
+
+ // From Layout:
+ virtual void removeItem(LayoutItem* item);
+ virtual void layout();
+
+ void setPosition(LayoutItem* item, GridRange const& range);
+
+ int lastUsedRow(int column) const;
+ int lastUsedColumn(int row) const;
+
+protected:
+ std::size_t number_of_columns;
+ std::size_t number_of_rows;
+
+ // Note: Yes, this is somewhat redundant to the LayoutItemList of the Layout
+ // class. However, this was the best idea I had such that I could still
+ // derive from Layout. If you find this ugly, feel free to fix it.
+ std::unordered_map<LayoutItem*, GridRange> grid_ranges;
+
+private:
+ struct CellSize {
+ std::size_t width;
+ std::size_t height;
+ };
+
+ CellSize calculateCellSize() const;
+ void moveAndResize(
+ LayoutItem& item, GridRange const& range, CellSize cell_size) const;
+};
+
+} // GUI::
diff --git a/dggui/led.cc b/dggui/led.cc
new file mode 100644
index 0000000..f77e31a
--- /dev/null
+++ b/dggui/led.cc
@@ -0,0 +1,97 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/***************************************************************************
+ * led.cc
+ *
+ * Sat Oct 15 19:12:33 CEST 2011
+ * Copyright 2011 Bent Bisballe Nyeng
+ * deva@aasimon.org
+ ****************************************************************************/
+
+/*
+ * This file is part of DrumGizmo.
+ *
+ * DrumGizmo is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * DrumGizmo is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with DrumGizmo; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+#include "led.h"
+
+#include "painter.h"
+
+namespace GUI {
+
+LED::LED(Widget *parent)
+ : Widget(parent)
+ , state(Off)
+{
+}
+
+void LED::setState(state_t state)
+{
+ if(this->state != state)
+ {
+ this->state = state;
+ redraw();
+ }
+}
+
+void LED::repaintEvent(RepaintEvent* repaintEvent)
+{
+ size_t h = height() - 1;
+ size_t w = width() - 1;
+
+ Painter p(*this);
+ float alpha = 0.9;
+ switch(state) {
+ case Red:
+ p.setColour(Colour(1, 0, 0,alpha));
+ break;
+ case Green:
+ p.setColour(Colour(0, 1, 0, alpha));
+ break;
+ case Blue:
+ p.setColour(Colour(0, 0, 1, alpha));
+ break;
+ case Off:
+ p.setColour(Colour(0.2, 0.2, 0.2, alpha));
+ break;
+ }
+
+ size_t size = w / 2;
+ if((h / 2) < size)
+ {
+ size = h / 2;
+ }
+ p.drawFilledCircle(w / 2, h / 2, size);
+
+ switch(state) {
+ case Red:
+ p.setColour(Colour(0.4, 0, 0, alpha));
+ break;
+ case Green:
+ p.setColour(Colour(0, 0.4, 0, alpha));
+ break;
+ case Blue:
+ p.setColour(Colour(0, 0, 0.4, alpha));
+ break;
+ case Off:
+ p.setColour(Colour(0.1, 0.1, 0.1, alpha));
+ break;
+ }
+ p.drawCircle(w / 2, h / 2, size);
+
+ p.setColour(Colour(1, alpha));
+ p.drawFilledCircle(w / 3, h / 3, size / 6);
+}
+
+} // GUI::
diff --git a/dggui/led.h b/dggui/led.h
new file mode 100644
index 0000000..14ab6ef
--- /dev/null
+++ b/dggui/led.h
@@ -0,0 +1,54 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/***************************************************************************
+ * led.h
+ *
+ * Sat Oct 15 19:12:33 CEST 2011
+ * Copyright 2011 Bent Bisballe Nyeng
+ * deva@aasimon.org
+ ****************************************************************************/
+
+/*
+ * This file is part of DrumGizmo.
+ *
+ * DrumGizmo is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * DrumGizmo is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with DrumGizmo; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+#pragma once
+
+#include "widget.h"
+
+namespace GUI {
+
+class LED : public Widget {
+public:
+ typedef enum {
+ Red,
+ Green,
+ Blue,
+ Off
+ } state_t;
+
+ LED(Widget *parent);
+
+ void setState(state_t state);
+
+protected:
+ // From Widget:
+ void repaintEvent(RepaintEvent* repaintEvent) override;
+
+private:
+ state_t state;
+};
+
+} // GUI::
diff --git a/dggui/lineedit.cc b/dggui/lineedit.cc
new file mode 100644
index 0000000..14cc234
--- /dev/null
+++ b/dggui/lineedit.cc
@@ -0,0 +1,285 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/***************************************************************************
+ * lineedit.cc
+ *
+ * Sun Oct 9 13:01:52 CEST 2011
+ * Copyright 2011 Bent Bisballe Nyeng
+ * deva@aasimon.org
+ ****************************************************************************/
+
+/*
+ * This file is part of DrumGizmo.
+ *
+ * DrumGizmo is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * DrumGizmo is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with DrumGizmo; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+#include "lineedit.h"
+
+#include <stdio.h>
+#include <hugin.hpp>
+
+#define BORDER 10
+
+namespace GUI {
+
+LineEdit::LineEdit(Widget *parent)
+ : Widget(parent)
+{
+ setReadOnly(false);
+}
+
+LineEdit::~LineEdit()
+{
+}
+
+void LineEdit::setReadOnly(bool ro)
+{
+ readonly = ro;
+}
+
+bool LineEdit::readOnly()
+{
+ return readonly;
+}
+
+void LineEdit::setText(const std::string& text)
+{
+ _text = text;
+ pos = text.size();
+
+ visibleText = _text;
+ offsetPos = 0;
+
+ redraw();
+ textChanged();
+}
+
+std::string LineEdit::getText()
+{
+ return _text;
+}
+
+void LineEdit::buttonEvent(ButtonEvent *buttonEvent)
+{
+ if(readOnly())
+ {
+ return;
+ }
+
+ // Ignore everything except left clicks.
+ if(buttonEvent->button != MouseButton::left)
+ {
+ return;
+ }
+
+ if(buttonEvent->direction == Direction::down)
+ {
+ for(int i = 0; i < (int)visibleText.length(); ++i)
+ {
+ int textWidth = font.textWidth(visibleText.substr(0, i));
+ if(buttonEvent->x < (textWidth + BORDER))
+ {
+ pos = i + offsetPos;
+ break;
+ }
+ }
+ redraw();
+ }
+}
+
+void LineEdit::keyEvent(KeyEvent *keyEvent)
+{
+ if(readOnly())
+ {
+ return;
+ }
+
+ bool change = false;
+
+ if(keyEvent->direction == Direction::down)
+ {
+ switch(keyEvent->keycode) {
+ case Key::left:
+ if(pos == 0)
+ {
+ return;
+ }
+
+ pos--;
+
+ if(offsetPos >= pos)
+ {
+ walkstate = WalkLeft;
+ }
+ break;
+
+ case Key::right:
+ if(pos == _text.length())
+ {
+ return;
+ }
+
+ pos++;
+
+ if((pos < _text.length()) && ((offsetPos + visibleText.length()) <= pos))
+ {
+ walkstate = WalkRight;
+ }
+ break;
+
+ case Key::home:
+ pos = 0;
+ visibleText = _text;
+ offsetPos = 0;
+ break;
+
+ case Key::end:
+ pos = _text.length();
+ visibleText = _text;
+ offsetPos = 0;
+ break;
+
+ case Key::deleteKey:
+ if(pos < _text.length())
+ {
+ std::string t = _text.substr(0, pos);
+ t += _text.substr(pos + 1, std::string::npos);
+ _text = t;
+ change = true;
+ }
+ break;
+
+ case Key::backspace:
+ if(pos > 0)
+ {
+ std::string t = _text.substr(0, pos - 1);
+ t += _text.substr(pos, std::string::npos);
+ _text = t;
+ pos--;
+ change = true;
+ }
+ break;
+
+ case Key::character:
+ {
+ std::string pre = _text.substr(0, pos);
+ std::string post = _text.substr(pos, std::string::npos);
+ _text = pre + keyEvent->text + post;
+ change = true;
+ pos++;
+ }
+ break;
+
+ case Key::enter:
+ enterPressedNotifier();
+ break;
+
+ default:
+ break;
+ }
+
+ redraw();
+ }
+
+ if(change)
+ {
+ textChanged();
+ }
+}
+
+void LineEdit::repaintEvent(RepaintEvent *repaintEvent)
+{
+ Painter p(*this);
+
+ int w = width();
+ int h = height();
+ if((w == 0) || (h == 0))
+ {
+ return;
+ }
+
+ box.setSize(w, h);
+ p.drawImage(0, 0, box);
+
+ p.setColour(Colour(183.0f/255.0f, 219.0f/255.0f, 255.0f/255.0f, 1.0f));
+
+ switch(walkstate) {
+ case WalkLeft:
+ visibleText = _text.substr(pos, std::string::npos);
+ offsetPos = pos;
+ break;
+
+ case WalkRight:
+ {
+ int delta = (offsetPos < _text.length()) ? 1 : 0;
+ visibleText = _text.substr(offsetPos + delta);
+ offsetPos = offsetPos + delta;
+ }
+ break;
+
+ case Noop:
+ visibleText = _text;
+ offsetPos = 0;
+ break;
+ }
+
+ while(true)
+ {
+ int textWidth = font.textWidth(visibleText);
+ if(textWidth <= std::max(w - BORDER - 4 + 3, 0))
+ {
+ break;
+ }
+
+ switch(walkstate) {
+ case WalkLeft:
+ visibleText = visibleText.substr(0, visibleText.length() - 1);
+ break;
+
+ case WalkRight:
+ visibleText = visibleText.substr(0, visibleText.length() - 1);
+ break;
+
+ case Noop:
+ if(offsetPos < pos)
+ {
+ visibleText = visibleText.substr(1);
+ offsetPos++;
+ }
+ else
+ {
+ visibleText = visibleText.substr(0, visibleText.length() - 1);
+ }
+ break;
+ }
+ }
+
+ walkstate = Noop;
+
+ p.drawText(BORDER - 4 + 3, height() / 2 + 5 + 1 + 1 + 1, font, visibleText);
+
+ if(readOnly())
+ {
+ return;
+ }
+
+ if(hasKeyboardFocus())
+ {
+ size_t px = font.textWidth(visibleText.substr(0, pos - offsetPos));
+ p.drawLine(px + BORDER - 1 - 4 + 3, 6,
+ px + BORDER - 1 - 4 + 3, height() - 7);
+ }
+}
+
+} // GUI::
diff --git a/dggui/lineedit.h b/dggui/lineedit.h
new file mode 100644
index 0000000..86ad986
--- /dev/null
+++ b/dggui/lineedit.h
@@ -0,0 +1,87 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/***************************************************************************
+ * lineedit.h
+ *
+ * Sun Oct 9 13:01:52 CEST 2011
+ * Copyright 2011 Bent Bisballe Nyeng
+ * deva@aasimon.org
+ ****************************************************************************/
+
+/*
+ * This file is part of DrumGizmo.
+ *
+ * DrumGizmo is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * DrumGizmo is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with DrumGizmo; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+#pragma once
+
+#include <string>
+
+#include "widget.h"
+#include "font.h"
+#include "painter.h"
+#include "texturedbox.h"
+
+namespace GUI
+{
+
+class LineEdit
+ : public Widget
+{
+public:
+ LineEdit(Widget *parent);
+ virtual ~LineEdit();
+
+ bool isFocusable() override { return true; }
+
+ std::string getText();
+ void setText(const std::string& text);
+
+ void setReadOnly(bool readonly);
+ bool readOnly();
+
+ Notifier<> enterPressedNotifier;
+
+ //protected:
+ virtual void keyEvent(KeyEvent *keyEvent) override;
+ virtual void repaintEvent(RepaintEvent *repaintEvent) override;
+ virtual void buttonEvent(ButtonEvent *buttonEvent) override;
+
+protected:
+ virtual void textChanged() {}
+
+private:
+ TexturedBox box{getImageCache(), ":resources/widget.png",
+ 0, 0, // atlas offset (x, y)
+ 7, 1, 7, // dx1, dx2, dx3
+ 7, 63, 7}; // dy1, dy2, dy3
+
+ Font font;
+
+ std::string _text;
+ size_t pos{0};
+ std::string visibleText;
+ size_t offsetPos{0};
+
+ enum state_t {
+ Noop,
+ WalkLeft,
+ WalkRight,
+ };
+ state_t walkstate{Noop};
+
+ bool readonly;
+};
+
+} // GUI::
diff --git a/dggui/listbox.cc b/dggui/listbox.cc
new file mode 100644
index 0000000..28c074e
--- /dev/null
+++ b/dggui/listbox.cc
@@ -0,0 +1,105 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/***************************************************************************
+ * listbox.cc
+ *
+ * Mon Feb 25 21:21:41 CET 2013
+ * Copyright 2013 Bent Bisballe Nyeng
+ * deva@aasimon.org
+ ****************************************************************************/
+
+/*
+ * This file is part of DrumGizmo.
+ *
+ * DrumGizmo is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * DrumGizmo is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with DrumGizmo; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+#include "listbox.h"
+
+#include "painter.h"
+#include "font.h"
+
+namespace GUI {
+
+ListBox::ListBox(Widget *parent)
+ : Widget(parent)
+ , selectionNotifier(basic.selectionNotifier)
+ , clickNotifier(basic.clickNotifier)
+ , valueChangedNotifier(basic.valueChangedNotifier)
+ , basic(this)
+{
+ basic.move(7, 7);
+}
+
+ListBox::~ListBox()
+{
+}
+
+void ListBox::addItem(std::string name, std::string value)
+{
+ basic.addItem(name, value);
+}
+
+void ListBox::addItems(std::vector<ListBoxBasic::Item> &items)
+{
+ basic.addItems(items);
+}
+
+void ListBox::clear()
+{
+ basic.clear();
+}
+
+bool ListBox::selectItem(int index)
+{
+ return basic.selectItem(index);
+}
+
+std::string ListBox::selectedName()
+{
+ return basic.selectedName();
+}
+
+std::string ListBox::selectedValue()
+{
+ return basic.selectedValue();
+}
+
+void ListBox::clearSelectedValue()
+{
+ basic.clearSelectedValue();
+}
+
+void ListBox::repaintEvent(RepaintEvent* repaintEvent)
+{
+ Painter p(*this);
+
+ int w = width();
+ int h = height();
+ if(w == 0 || h == 0)
+ {
+ return;
+ }
+
+ box.setSize(w, h);
+ p.drawImage(0, 0, box);
+}
+
+void ListBox::resize(std::size_t width, std::size_t height)
+{
+ Widget::resize(width, height);
+ basic.resize(width - (7 + 7),
+ height - (7 + 7));
+}
+
+} // GUI::
diff --git a/dggui/listbox.h b/dggui/listbox.h
new file mode 100644
index 0000000..0d9ad4d
--- /dev/null
+++ b/dggui/listbox.h
@@ -0,0 +1,74 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/***************************************************************************
+ * listbox.h
+ *
+ * Mon Feb 25 21:21:40 CET 2013
+ * Copyright 2013 Bent Bisballe Nyeng
+ * deva@aasimon.org
+ ****************************************************************************/
+
+/*
+ * This file is part of DrumGizmo.
+ *
+ * DrumGizmo is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * DrumGizmo is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with DrumGizmo; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+#pragma once
+
+#include <string.h>
+#include <vector>
+
+#include "widget.h"
+#include "painter.h"
+#include "listboxbasic.h"
+#include "texturedbox.h"
+
+namespace GUI
+{
+
+class ListBox
+ : public Widget
+{
+public:
+ ListBox(Widget *parent);
+ virtual ~ListBox();
+
+ void addItem(std::string name, std::string value);
+ void addItems(std::vector<ListBoxBasic::Item> &items);
+
+ void clear();
+ bool selectItem(int index);
+ std::string selectedName();
+ std::string selectedValue();
+ void clearSelectedValue();
+
+ // From Widget:
+ virtual void repaintEvent(RepaintEvent* repaintEvent) override;
+ virtual void resize(std::size_t width, std::size_t height) override;
+
+ // Forwarded notifiers from ListBoxBasic::basic
+ Notifier<>& selectionNotifier;
+ Notifier<>& clickNotifier;
+ Notifier<>& valueChangedNotifier;
+
+private:
+ ListBoxBasic basic;
+
+ TexturedBox box{getImageCache(), ":resources/widget.png",
+ 0, 0, // atlas offset (x, y)
+ 7, 1, 7, // dx1, dx2, dx3
+ 7, 63, 7}; // dy1, dy2, dy3
+};
+
+} // GUI::
diff --git a/dggui/listboxbasic.cc b/dggui/listboxbasic.cc
new file mode 100644
index 0000000..b2637eb
--- /dev/null
+++ b/dggui/listboxbasic.cc
@@ -0,0 +1,362 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/***************************************************************************
+ * listboxbasic.cc
+ *
+ * Thu Apr 4 20:28:10 CEST 2013
+ * Copyright 2013 Bent Bisballe Nyeng
+ * deva@aasimon.org
+ ****************************************************************************/
+
+/*
+ * This file is part of DrumGizmo.
+ *
+ * DrumGizmo is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * DrumGizmo is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with DrumGizmo; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+#include "listboxbasic.h"
+
+#include "painter.h"
+#include "font.h"
+
+namespace GUI {
+
+ListBoxBasic::ListBoxBasic(Widget *parent)
+ : Widget(parent)
+ , scroll(this)
+{
+ scroll.move(0,0);
+ scroll.resize(16, 100);
+
+ CONNECT(&scroll, valueChangeNotifier,
+ this, &ListBoxBasic::onScrollBarValueChange);
+
+ padding = 4;
+ btn_size = 18;
+
+ selected = -1;
+ marked = -1;
+}
+
+ListBoxBasic::~ListBoxBasic()
+{
+}
+
+void ListBoxBasic::setSelection(int index)
+{
+ selected = index;
+ if(marked == -1)
+ {
+ marked = index;
+ }
+ valueChangedNotifier();
+}
+
+void ListBoxBasic::addItem(const std::string& name, const std::string& value)
+{
+ std::vector<ListBoxBasic::Item> items;
+ ListBoxBasic::Item item;
+ item.name = name;
+ item.value = value;
+ items.push_back(item);
+ addItems(items);
+}
+
+void ListBoxBasic::addItems(const std::vector<ListBoxBasic::Item>& newItems)
+{
+ for(auto& item : newItems)
+ {
+ items.push_back(item);
+ }
+
+ if(selected == -1)
+ {
+ //setSelection((int)items.size() - 1);
+ setSelection(0);
+ }
+ redraw();
+}
+
+void ListBoxBasic::clear()
+{
+ items.clear();
+ setSelection(-1);
+ marked = -1;
+ scroll.setValue(0);
+ redraw();
+}
+
+bool ListBoxBasic::selectItem(int index)
+{
+ if(index < 0 || (index > (int)items.size() - 1))
+ {
+ return false;
+ }
+
+ setSelection(index);
+ redraw();
+
+ return true;
+}
+
+std::string ListBoxBasic::selectedName()
+{
+ if(selected < 0 || (selected > (int)items.size() - 1))
+ {
+ return "";
+ }
+
+ return items[selected].name;
+}
+
+std::string ListBoxBasic::selectedValue()
+{
+ if(selected < 0 || (selected > (int)items.size() - 1))
+ {
+ return "";
+ }
+
+ return items[selected].value;
+}
+
+void ListBoxBasic::clearSelectedValue()
+{
+ setSelection(-1);
+}
+
+void ListBoxBasic::onScrollBarValueChange(int value)
+{
+ redraw();
+}
+
+void ListBoxBasic::repaintEvent(RepaintEvent* repaintEvent)
+{
+ Painter p(*this);
+
+ int w = width();
+ int h = height();
+
+ if((w == 0) || (h == 0))
+ {
+ return;
+ }
+
+ p.drawImageStretched(0, 0, bg_img, w, h);
+
+ p.setColour(Colour(183.0f/255.0f, 219.0f/255.0f, 255.0f/255.0f, 1.0f));
+
+ int yoffset = padding / 2;
+ int skip = scroll.value();
+ int numitems = height() / (font.textHeight() + padding) + 1;
+ for(int idx = skip; (idx < (int)items.size()) && (idx < (skip + numitems));
+ idx++)
+ {
+ auto& item = items[idx];
+ if(idx == selected)
+ {
+ p.setColour(Colour(183.0f/255.0f, 219.0f/255.0f, 255.0f/255.0f, 0.5f));
+ p.drawFilledRectangle(0,
+ yoffset - (padding / 2),
+ width() - 1,
+ yoffset + (font.textHeight() + 1));
+ }
+
+ if(idx == marked)
+ {
+ p.drawRectangle(0,
+ yoffset - (padding / 2),
+ width() - 1,
+ yoffset + (font.textHeight() + 1));
+ }
+
+ p.setColour(Colour(183.0f/255.0f, 219.0f/255.0f, 255.0f/255.0f, 1.0f));
+
+ p.drawText(2, yoffset + font.textHeight(), font, item.name);
+ yoffset += font.textHeight() + padding;
+ }
+
+ scroll.setRange(numitems);
+ scroll.setMaximum(items.size());
+}
+
+void ListBoxBasic::scrollEvent(ScrollEvent* scrollEvent)
+{
+ // forward scroll event to scroll bar.
+ scroll.scrollEvent(scrollEvent);
+}
+
+void ListBoxBasic::keyEvent(KeyEvent* keyEvent)
+{
+ if(keyEvent->direction != Direction::down)
+ {
+ return;
+ }
+
+ switch(keyEvent->keycode) {
+ case Key::up:
+ if(marked == 0)
+ {
+ return;
+ }
+
+ marked--;
+
+ if(marked < scroll.value())
+ {
+ scroll.setValue(marked);
+ }
+ break;
+
+ case Key::down:
+ {
+ // Number of items that can be displayed at a time.
+ int numitems = height() / (font.textHeight() + padding);
+
+ if(marked == ((int)items.size() - 1))
+ {
+ return;
+ }
+
+ marked++;
+
+ if(marked > (scroll.value() + numitems - 1))
+ {
+ scroll.setValue(marked - numitems + 1);
+ }
+ }
+ break;
+
+ case Key::home:
+ marked = 0;
+ if(marked < scroll.value())
+ {
+ scroll.setValue(marked);
+ }
+ break;
+
+ case Key::end:
+ {
+ // Number of items that can be displayed at a time.
+ int numitems = height() / (font.textHeight() + padding);
+
+ marked = (int)items.size() - 1;
+ if(marked > (scroll.value() + numitems - 1))
+ {
+ scroll.setValue(marked - numitems + 1);
+ }
+ }
+ break;
+
+ case Key::character:
+ if(keyEvent->text == " ")
+ {
+ setSelection(marked);
+ //selectionNotifier();
+ }
+ break;
+
+ case Key::enter:
+ setSelection(marked);
+ selectionNotifier();
+ break;
+
+ default:
+ break;
+ }
+
+ redraw();
+}
+
+void ListBoxBasic::buttonEvent(ButtonEvent* buttonEvent)
+{
+ // Ignore everything except left clicks.
+ if(buttonEvent->button != MouseButton::left)
+ {
+ return;
+ }
+
+ if((buttonEvent->x > ((int)width() - btn_size)) &&
+ (buttonEvent->y < ((int)width() - 1)))
+ {
+ if(buttonEvent->y > 0 && buttonEvent->y < btn_size)
+ {
+ if(buttonEvent->direction == Direction::up)
+ {
+ return;
+ }
+ scroll.setValue(scroll.value() - 1);
+ return;
+ }
+
+ if(buttonEvent->y > ((int)height() - btn_size) &&
+ buttonEvent->y < ((int)height() - 1))
+ {
+ if(buttonEvent->direction == Direction::up)
+ {
+ return;
+ }
+ scroll.setValue(scroll.value() + 1);
+ return;
+ }
+ }
+
+ if(buttonEvent->direction == Direction::up)
+ {
+ int skip = scroll.value();
+ size_t yoffset = padding / 2;
+ for(int idx = skip; idx < (int)items.size(); idx++)
+ {
+ yoffset += font.textHeight() + padding;
+ if(buttonEvent->y < (int)yoffset - (padding / 2))
+ {
+ setSelection(idx);
+ marked = selected;
+ clickNotifier();
+ break;
+ }
+ }
+
+ redraw();
+ }
+
+ if(buttonEvent->direction != Direction::up)
+ {
+ int skip = scroll.value();
+ size_t yoffset = padding / 2;
+ for(int idx = skip; idx < (int)items.size(); idx++)
+ {
+ yoffset += font.textHeight() + padding;
+ if(buttonEvent->y < ((int)yoffset - (padding / 2)))
+ {
+ marked = idx;
+ break;
+ }
+ }
+
+ redraw();
+ }
+
+ if(buttonEvent->doubleClick)
+ {
+ selectionNotifier();
+ }
+}
+
+void ListBoxBasic::resize(std::size_t width, std::size_t height)
+{
+ Widget::resize(width, height);
+ scroll.move(width - scroll.width(), 0);
+ scroll.resize(scroll.width(), height);
+}
+
+} // GUI::
diff --git a/dggui/listboxbasic.h b/dggui/listboxbasic.h
new file mode 100644
index 0000000..27822e6
--- /dev/null
+++ b/dggui/listboxbasic.h
@@ -0,0 +1,94 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/***************************************************************************
+ * listboxbasic.h
+ *
+ * Thu Apr 4 20:28:10 CEST 2013
+ * Copyright 2013 Bent Bisballe Nyeng
+ * deva@aasimon.org
+ ****************************************************************************/
+
+/*
+ * This file is part of DrumGizmo.
+ *
+ * DrumGizmo is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * DrumGizmo is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with DrumGizmo; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+#pragma once
+
+#include <string.h>
+#include <vector>
+
+#include <notifier.h>
+
+#include "widget.h"
+#include "font.h"
+#include "painter.h"
+#include "scrollbar.h"
+
+namespace GUI {
+
+class ListBoxBasic : public Widget {
+public:
+ class Item {
+ public:
+ std::string name;
+ std::string value;
+ };
+
+ ListBoxBasic(Widget *parent);
+ virtual ~ListBoxBasic();
+
+ void addItem(const std::string& name, const std::string& value);
+ void addItems(const std::vector<Item>& items);
+
+ void clear();
+ bool selectItem(int index);
+ std::string selectedName();
+ std::string selectedValue();
+
+ void clearSelectedValue();
+
+ Notifier<> selectionNotifier;
+ Notifier<> clickNotifier;
+ Notifier<> valueChangedNotifier;
+
+ // From Widget:
+ virtual void resize(std::size_t width, std::size_t height) override;
+
+protected:
+ void onScrollBarValueChange(int value);
+
+ // From Widget:
+ bool isFocusable() override { return true; }
+ virtual void repaintEvent(RepaintEvent* repaintEvent) override;
+ virtual void buttonEvent(ButtonEvent* buttonEvent) override;
+ virtual void keyEvent(KeyEvent* keyEvent) override;
+ virtual void scrollEvent(ScrollEvent* scrollEvent) override;
+
+ ScrollBar scroll;
+
+ Texture bg_img{getImageCache(), ":resources/widget.png", 7, 7, 1, 63};
+
+ void setSelection(int index);
+
+ std::vector<Item> items;
+
+ int selected;
+ int marked;
+ Font font;
+ int padding;
+ int btn_size;
+};
+
+} // GUI::
diff --git a/dggui/listboxthin.cc b/dggui/listboxthin.cc
new file mode 100644
index 0000000..d224f11
--- /dev/null
+++ b/dggui/listboxthin.cc
@@ -0,0 +1,101 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/***************************************************************************
+ * listboxthin.cc
+ *
+ * Sun Apr 7 19:39:36 CEST 2013
+ * Copyright 2013 Bent Bisballe Nyeng
+ * deva@aasimon.org
+ ****************************************************************************/
+
+/*
+ * This file is part of DrumGizmo.
+ *
+ * DrumGizmo is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * DrumGizmo is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with DrumGizmo; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+#include "listboxthin.h"
+
+#include "painter.h"
+#include "font.h"
+
+namespace GUI
+{
+
+ListBoxThin::ListBoxThin(Widget *parent)
+ : Widget(parent)
+ , selectionNotifier(basic.selectionNotifier)
+ , clickNotifier(basic.clickNotifier)
+ , valueChangedNotifier(basic.valueChangedNotifier)
+ , basic(this)
+{
+ basic.move(1, 1);
+}
+
+ListBoxThin::~ListBoxThin()
+{
+}
+
+void ListBoxThin::addItem(std::string name, std::string value)
+{
+ basic.addItem(name, value);
+}
+
+void ListBoxThin::addItems(std::vector<ListBoxBasic::Item> &items)
+{
+ basic.addItems(items);
+}
+
+void ListBoxThin::clear()
+{
+ basic.clear();
+}
+
+bool ListBoxThin::selectItem(int index)
+{
+ return basic.selectItem(index);
+}
+
+std::string ListBoxThin::selectedName()
+{
+ return basic.selectedName();
+}
+
+std::string ListBoxThin::selectedValue()
+{
+ return basic.selectedValue();
+}
+
+void ListBoxThin::repaintEvent(RepaintEvent* repaintEvent)
+{
+ Painter p(*this);
+
+ int w = width();
+ int h = height();
+ if(w == 0 || h == 0)
+ {
+ return;
+ }
+
+ box.setSize(w,h);
+ p.drawImage(0, 0, box);
+}
+
+void ListBoxThin::resize(std::size_t height, std::size_t width)
+{
+ Widget::resize(width, height);
+ basic.resize(width - (1 + 1),
+ height - (1 + 1));
+}
+
+} // GUI::
diff --git a/dggui/listboxthin.h b/dggui/listboxthin.h
new file mode 100644
index 0000000..e861745
--- /dev/null
+++ b/dggui/listboxthin.h
@@ -0,0 +1,75 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/***************************************************************************
+ * listboxthin.h
+ *
+ * Sun Apr 7 19:39:35 CEST 2013
+ * Copyright 2013 Bent Bisballe Nyeng
+ * deva@aasimon.org
+ ****************************************************************************/
+
+/*
+ * This file is part of DrumGizmo.
+ *
+ * DrumGizmo is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * DrumGizmo is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with DrumGizmo; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+#pragma once
+
+#include <string.h>
+#include <vector>
+
+#include <notifier.h>
+
+#include "widget.h"
+#include "painter.h"
+#include "listboxbasic.h"
+#include "texturedbox.h"
+
+namespace GUI
+{
+
+class ListBoxThin
+ : public Widget
+{
+public:
+ ListBoxThin(Widget *parent);
+ virtual ~ListBoxThin();
+
+ void addItem(std::string name, std::string value);
+ void addItems(std::vector<ListBoxBasic::Item> &items);
+
+ void clear();
+ bool selectItem(int index);
+ std::string selectedName();
+ std::string selectedValue();
+
+ // From Widget:
+ virtual void repaintEvent(GUI::RepaintEvent* repaintEvent) override;
+ virtual void resize(std::size_t height, std::size_t width) override;
+
+ // Forwarded notifier from ListBoxBasic::basic
+ Notifier<>& selectionNotifier;
+ Notifier<>& clickNotifier;
+ Notifier<>& valueChangedNotifier;
+
+private:
+ ListBoxBasic basic;
+
+ TexturedBox box{getImageCache(), ":resources/thinlistbox.png",
+ 0, 0, // atlas offset (x, y)
+ 1, 1, 1, // dx1, dx2, dx3
+ 1, 1, 1}; // dy1, dy2, dy3
+};
+
+} // GUI::
diff --git a/dggui/lodepng b/dggui/lodepng
new file mode 160000
+Subproject a71964ed5fe4f82a32ac7f8201338900f66e855
diff --git a/dggui/nativewindow.h b/dggui/nativewindow.h
new file mode 100644
index 0000000..400ff57
--- /dev/null
+++ b/dggui/nativewindow.h
@@ -0,0 +1,100 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/***************************************************************************
+ * nativewindow.h
+ *
+ * Fri Dec 28 18:46:01 CET 2012
+ * Copyright 2012 Bent Bisballe Nyeng
+ * deva@aasimon.org
+ ****************************************************************************/
+
+/*
+ * This file is part of DrumGizmo.
+ *
+ * DrumGizmo is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * DrumGizmo is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with DrumGizmo; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+#pragma once
+
+#include <string>
+#include <memory>
+#include <queue>
+#include <tuple>
+#include <vector>
+
+#include "guievent.h"
+
+namespace GUI
+{
+
+struct Point;
+
+//! Interface class for native window implementations.
+class NativeWindow
+{
+public:
+ NativeWindow() {}
+ virtual ~NativeWindow() {}
+
+ //! Set a fixed size to the window.
+ //! It resizes the window and disallows user resizing.
+ virtual void setFixedSize(std::size_t width, std::size_t height) = 0;
+
+ //! Force window to stay on top of other windows
+ virtual void setAlwaysOnTop(bool always_on_top) = 0;
+
+ //! Set a new size of the window.
+ virtual void resize(std::size_t width, std::size_t height) = 0;
+
+ //! Query size of the native window.
+ virtual std::pair<std::size_t, std::size_t> getSize() const = 0;
+
+ //! Move the window to a new position.
+ //! Note: negative value are allowed.
+ virtual void move(int x, int y) = 0;
+
+ //! Query the screen position of the native window.
+ //! Note: returned values can be negative.
+ virtual std::pair<int, int> getPosition() const = 0;
+
+ //! Show the window if it is hidden.
+ virtual void show() = 0;
+
+ //! Hides the window.
+ virtual void hide() = 0;
+
+ //! Return visibility state of the native window.
+ virtual bool visible() const = 0;
+
+ //! Sets the window caption in the title bar (if it has one).
+ virtual void setCaption(const std::string &caption) = 0;
+
+ //! Draw the internal rendering buffer to the window buffer.
+ virtual void redraw(const Rect& dirty_rect) = 0;
+
+ //! Toggle capture mouse mode.
+ virtual void grabMouse(bool grab) = 0;
+
+ //! Reads all currently enqueued events from the native window system.
+ //! \return A queue of shared pointers to events.
+ virtual EventQueue getEvents() = 0;
+
+ //! \returns the native window handle, it HWND on Win32 or Window id on X11
+ virtual void* getNativeWindowHandle() const = 0;
+
+ //! Translate a the local native window coordinate to a global screen
+ //! coordinate.
+ virtual Point translateToScreen(const Point& point) = 0;
+};
+
+} // GUI::
diff --git a/dggui/nativewindow_cocoa.h b/dggui/nativewindow_cocoa.h
new file mode 100644
index 0000000..8dc73e6
--- /dev/null
+++ b/dggui/nativewindow_cocoa.h
@@ -0,0 +1,78 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/***************************************************************************
+ * nativewindow_cocoa.h
+ *
+ * Sun Dec 4 15:55:14 CET 2016
+ * Copyright 2016 Bent Bisballe Nyeng
+ * deva@aasimon.org
+ ****************************************************************************/
+
+/*
+ * This file is part of DrumGizmo.
+ *
+ * DrumGizmo is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * DrumGizmo is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with DrumGizmo; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+#pragma once
+
+#include <memory>
+
+#include "nativewindow.h"
+
+namespace GUI
+{
+
+class Window;
+class NativeWindowCocoa
+ : public NativeWindow
+{
+public:
+ NativeWindowCocoa(void* native_window, Window& window);
+ ~NativeWindowCocoa();
+
+ // From NativeWindow:
+ virtual void setFixedSize(std::size_t width, std::size_t height) override;
+ virtual void setAlwaysOnTop(bool always_on_top) override;
+ virtual void resize(std::size_t width, std::size_t height) override;
+ virtual std::pair<std::size_t, std::size_t> getSize() const override;
+ virtual void move(int x, int y) override;
+ virtual std::pair<int, int> getPosition() const override;
+ virtual void show() override;
+ virtual void hide() override;
+ virtual bool visible() const override;
+ virtual void setCaption(const std::string &caption) override;
+ virtual void redraw(const Rect& dirty_rect) override;
+ virtual void grabMouse(bool grab) override;
+ virtual EventQueue getEvents() override;
+ virtual void* getNativeWindowHandle() const override;
+ virtual Point translateToScreen(const Point& point) override;
+
+ // Expose friend members of Window to ObjC++ implementation.
+ class Window& getWindow();
+ class PixelBuffer& getWindowPixbuf();
+ void resized();
+ void pushBackEvent(std::shared_ptr<Event> event);
+
+private:
+ void updateLayerOffset();
+
+ Window& window;
+ std::unique_ptr<struct priv> priv;
+ EventQueue event_queue;
+ void* native_window{nullptr};
+ bool first{true};
+ float scale{1.0};
+};
+
+} // GUI::
diff --git a/dggui/nativewindow_cocoa.mm b/dggui/nativewindow_cocoa.mm
new file mode 100644
index 0000000..7b6ecc8
--- /dev/null
+++ b/dggui/nativewindow_cocoa.mm
@@ -0,0 +1,832 @@
+/* -*- Mode: ObjC; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/***************************************************************************
+ * nativewindow_cocoa.mm
+ *
+ * Fri Dec 2 20:31:03 CET 2016
+ * Copyright 2016 Bent Bisballe Nyeng
+ * deva@aasimon.org
+ ****************************************************************************/
+
+/*
+ * This file is part of DrumGizmo.
+ *
+ * DrumGizmo is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * DrumGizmo is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with DrumGizmo; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+#include "nativewindow_cocoa.h"
+
+#include "guievent.h"
+
+#include <stdio.h>
+#include <unistd.h>
+
+#import <Cocoa/Cocoa.h>
+
+#include "window.h"
+
+#include <Availability.h>
+
+#ifdef __MAC_OS_X_VERSION_MAX_ALLOWED
+#if __MAC_OS_X_VERSION_MAX_ALLOWED < 101300 // Before MacOSX 10.13 (High-Sierra)
+#define STYLE_MASK \
+ (NSClosableWindowMask | \
+ NSTitledWindowMask | \
+ NSResizableWindowMask)
+#define IMAGE_FLAGS \
+ (kCGBitmapByteOrder32Big | \
+ kCGImageAlphaPremultipliedLast)
+#define EVENT_MASK \
+ NSAnyEventMask
+#else
+#define STYLE_MASK \
+ (NSWindowStyleMaskClosable | \
+ NSWindowStyleMaskTitled | \
+ NSWindowStyleMaskResizable)
+#define IMAGE_FLAGS \
+ (kCGImageByteOrder32Big | \
+ kCGImageAlphaPremultipliedLast)
+#define EVENT_MASK \
+ NSEventMaskAny
+#endif
+
+#if __MAC_OS_X_VERSION_MAX_ALLOWED < 101400 // Before MacOSX 10.14 (Mojave)
+// Nothing here yet...
+#endif
+#endif
+
+@interface DGListener : NSWindow
+{
+@public
+ NSWindow* window;
+ GUI::NativeWindowCocoa* native;
+}
+
+- (id) initWithWindow:(NSWindow*)ref
+ native:(GUI::NativeWindowCocoa*)_native;
+- (void) dealloc;
+- (void) windowDidResize;
+- (void) windowWillResize;
+- (void) windowWillClose;
+- (void) unbindNative;
+@end
+
+@implementation DGListener
+- (id) initWithWindow:(NSWindow*)ref
+ native:(GUI::NativeWindowCocoa*)_native
+{
+ [super init];
+
+ native = _native;
+ window = ref;
+
+ [[NSNotificationCenter defaultCenter]
+ addObserver:self
+ selector:@selector(windowDidResize)
+ name:NSWindowDidResizeNotification
+ object:ref];
+
+ [[NSNotificationCenter defaultCenter]
+ addObserver:self
+ selector:@selector(windowWillResize)
+ name:NSWindowWillStartLiveResizeNotification
+ object:ref];
+
+ [[NSNotificationCenter defaultCenter]
+ addObserver:self
+ selector:@selector(windowWillClose)
+ name:NSWindowWillCloseNotification
+ object:ref];
+
+ [self windowWillResize]; // trigger to get the initial size as a size change
+
+ return self;
+}
+
+- (void) dealloc
+{
+ [[NSNotificationCenter defaultCenter] removeObserver:self];
+ [super dealloc];
+}
+
+- (void)windowDidResize
+{
+ if(!native)
+ {
+ return;
+ }
+
+ native->resized();
+}
+
+- (void)windowWillResize
+{
+ if(!native)
+ {
+ return;
+ }
+
+ native->resized();
+}
+
+- (void) windowWillClose
+{
+ if(!native)
+ {
+ return;
+ }
+
+ auto closeEvent = std::make_shared<GUI::CloseEvent>();
+ native->pushBackEvent(closeEvent);
+}
+
+- (void) unbindNative
+{
+ native = nullptr;
+}
+@end
+
+@interface DGView : NSView
+{
+ int colorBits;
+ int depthBits;
+
+@private
+ GUI::NativeWindowCocoa* native;
+ NSTrackingArea* trackingArea;
+}
+
+//- (id) initWithFrame:(NSRect)frame
+// colorBits:(int)numColorBits
+// depthBits:(int)numDepthBits;
+- (void) updateTrackingAreas;
+
+- (void) mouseEntered:(NSEvent *)event;
+- (void) mouseExited:(NSEvent *)event;
+- (void) mouseMoved:(NSEvent*)event;
+- (void) mouseDown:(NSEvent*)event;
+- (void) mouseUp:(NSEvent*)event;
+- (void) rightMouseDown:(NSEvent*)event;
+- (void) rightMouseUp:(NSEvent*)event;
+- (void) otherMouseDown:(NSEvent*)event;
+- (void) otherMouseUp:(NSEvent*)event;
+- (void) mouseDragged:(NSEvent*)event;
+- (void) rightMouseDragged:(NSEvent*)event;
+- (void) otherMouseDragged:(NSEvent*)event;
+- (void) scrollWheel:(NSEvent*)event;
+- (void) keyDown:(NSEvent*)event;
+- (void) keyUp:(NSEvent*)event;
+
+- (void) dealloc;
+- (void) bindNative:(GUI::NativeWindowCocoa*)native;
+- (void) unbindNative;
+@end
+
+@implementation DGView
+//- (id) initWithFrame:(NSRect)frame
+// colorBits:(int)numColorBits
+// depthBits:(int)numDepthBits
+//{
+// [super init];
+// [self updateTrackingAreas];
+// return self;
+//}
+
+- (void) updateTrackingAreas
+{
+ if(trackingArea != nil)
+ {
+ [self removeTrackingArea:trackingArea];
+ [trackingArea release];
+ }
+
+ int opts =
+ NSTrackingMouseEnteredAndExited |
+ NSTrackingMouseMoved |
+ NSTrackingActiveAlways;
+
+ trackingArea =
+ [[NSTrackingArea alloc] initWithRect:[self bounds]
+ options:opts
+ owner:self
+ userInfo:nil];
+ [self addTrackingArea:trackingArea];
+}
+
+- (void) mouseEntered:(NSEvent *)event
+{
+ [super mouseEntered:event];
+ auto frame = [self frame];
+ NSPoint loc = [event locationInWindow];
+ auto mouseEnterEvent = std::make_shared<GUI::MouseEnterEvent>();
+ mouseEnterEvent->x = loc.x - frame.origin.x;
+ mouseEnterEvent->y = frame.size.height - loc.y - frame.origin.y;
+ native->pushBackEvent(mouseEnterEvent);
+ //[[NSCursor pointingHandCursor] set];
+}
+
+- (void) mouseExited:(NSEvent *)event
+{
+ [super mouseExited:event];
+ auto frame = [self frame];
+ NSPoint loc = [event locationInWindow];
+ auto mouseLeaveEvent = std::make_shared<GUI::MouseLeaveEvent>();
+ mouseLeaveEvent->x = loc.x - frame.origin.x;
+ mouseLeaveEvent->y = frame.size.height - loc.y - frame.origin.y;
+ native->pushBackEvent(mouseLeaveEvent);
+ //[[NSCursor arrowCursor] set];
+}
+
+- (void) mouseMoved:(NSEvent*)event
+{
+ auto frame = [self frame];
+ NSPoint loc = [event locationInWindow];
+ auto mouseMoveEvent = std::make_shared<GUI::MouseMoveEvent>();
+ mouseMoveEvent->x = loc.x - frame.origin.x;
+ mouseMoveEvent->y = frame.size.height - loc.y - frame.origin.y;
+ native->pushBackEvent(mouseMoveEvent);
+}
+
+- (void) mouseDown:(NSEvent*)event
+{
+ auto frame = [self frame];
+ NSPoint loc = [event locationInWindow];
+
+ auto buttonEvent = std::make_shared<GUI::ButtonEvent>();
+ buttonEvent->x = loc.x - frame.origin.x;
+ buttonEvent->y = frame.size.height - loc.y - frame.origin.y;
+ switch((int)[event buttonNumber])
+ {
+ case 0:
+ buttonEvent->button = GUI::MouseButton::left;
+ break;
+ case 1:
+ buttonEvent->button = GUI::MouseButton::right;
+ break;
+ case 2:
+ buttonEvent->button = GUI::MouseButton::middle;
+ break;
+ default:
+ return;
+ }
+ buttonEvent->direction = GUI::Direction::down;
+ buttonEvent->doubleClick = [event clickCount] == 2;
+ native->pushBackEvent(buttonEvent);
+
+ [super mouseDown: event];
+}
+
+- (void) mouseUp:(NSEvent*)event
+{
+ auto frame = [self frame];
+ NSPoint loc = [event locationInWindow];
+
+ auto buttonEvent = std::make_shared<GUI::ButtonEvent>();
+ buttonEvent->x = loc.x - frame.origin.x;
+ buttonEvent->y = frame.size.height - loc.y - frame.origin.y;
+ switch((int)[event buttonNumber])
+ {
+ case 0:
+ buttonEvent->button = GUI::MouseButton::left;
+ break;
+ case 1:
+ buttonEvent->button = GUI::MouseButton::right;
+ break;
+ case 2:
+ buttonEvent->button = GUI::MouseButton::middle;
+ break;
+ default:
+ return;
+ }
+ buttonEvent->direction = GUI::Direction::up;
+ buttonEvent->doubleClick = false;
+ native->pushBackEvent(buttonEvent);
+
+ [super mouseUp: event];
+}
+
+- (void) rightMouseDown:(NSEvent*)event
+{
+ [self mouseDown: event];
+ [super rightMouseDown: event];
+}
+
+- (void) rightMouseUp:(NSEvent*)event
+{
+ [self mouseUp: event];
+ [super rightMouseUp: event];
+}
+
+- (void) otherMouseDown:(NSEvent*)event
+{
+ [self mouseDown: event];
+ [super otherMouseDown: event];
+}
+
+- (void) otherMouseUp:(NSEvent*)event
+{
+ [self mouseUp: event];
+ [super otherMouseUp: event];
+}
+
+- (void) mouseDragged:(NSEvent*)event
+{
+ [self mouseMoved: event];
+ [super mouseDragged: event];
+}
+
+- (void) rightMouseDragged:(NSEvent*)event
+{
+ [self mouseMoved: event];
+ [super rightMouseDragged: event];
+}
+
+- (void) otherMouseDragged:(NSEvent*)event
+{
+ [self mouseMoved: event];
+ [super otherMouseDragged: event];
+}
+
+- (void) scrollWheel:(NSEvent*)event
+{
+ auto frame = [self frame];
+ NSPoint loc = [event locationInWindow];
+
+ auto scrollEvent = std::make_shared<GUI::ScrollEvent>();
+ scrollEvent->x = loc.x - frame.origin.x;
+ scrollEvent->y = frame.size.height - loc.y - frame.origin.y;
+ scrollEvent->delta = [event deltaY] * -1.0f;
+ native->pushBackEvent(scrollEvent);
+
+ [super scrollWheel: event];
+}
+
+- (void) keyDown:(NSEvent*)event
+{
+ const NSString* chars = [event characters];
+ const char* str = [chars UTF8String];
+
+ auto keyEvent = std::make_shared<GUI::KeyEvent>();
+
+ switch([event keyCode])
+ {
+ case 123: keyEvent->keycode = GUI::Key::left; break;
+ case 124: keyEvent->keycode = GUI::Key::right; break;
+ case 126: keyEvent->keycode = GUI::Key::up; break;
+ case 125: keyEvent->keycode = GUI::Key::down; break;
+ case 117: keyEvent->keycode = GUI::Key::deleteKey; break;
+ case 51: keyEvent->keycode = GUI::Key::backspace; break;
+ case 115: keyEvent->keycode = GUI::Key::home; break;
+ case 119: keyEvent->keycode = GUI::Key::end; break;
+ case 121: keyEvent->keycode = GUI::Key::pageDown; break;
+ case 116: keyEvent->keycode = GUI::Key::pageUp; break;
+ case 36: keyEvent->keycode = GUI::Key::enter; break;
+ default: keyEvent->keycode = GUI::Key::unknown; break;
+ }
+
+ if(strlen(str) && keyEvent->keycode == GUI::Key::unknown)
+ {
+ keyEvent->keycode = GUI::Key::character;
+ }
+
+ keyEvent->text = str; // TODO: UTF8 decode
+ keyEvent->direction = GUI::Direction::down;
+
+ native->pushBackEvent(keyEvent);
+ [super keyDown: event];
+}
+
+- (void) keyUp:(NSEvent*)event
+{
+ const NSString* chars = [event characters];
+ const char* str = [chars UTF8String];
+ auto keyEvent = std::make_shared<GUI::KeyEvent>();
+
+ switch([event keyCode])
+ {
+ case 123: keyEvent->keycode = GUI::Key::left; break;
+ case 124: keyEvent->keycode = GUI::Key::right; break;
+ case 126: keyEvent->keycode = GUI::Key::up; break;
+ case 125: keyEvent->keycode = GUI::Key::down; break;
+ case 117: keyEvent->keycode = GUI::Key::deleteKey; break;
+ case 51: keyEvent->keycode = GUI::Key::backspace; break;
+ case 115: keyEvent->keycode = GUI::Key::home; break;
+ case 119: keyEvent->keycode = GUI::Key::end; break;
+ case 121: keyEvent->keycode = GUI::Key::pageDown; break;
+ case 116: keyEvent->keycode = GUI::Key::pageUp; break;
+ case 36: keyEvent->keycode = GUI::Key::enter; break;
+ default: keyEvent->keycode = GUI::Key::unknown; break;
+ }
+
+ if(strlen(str) && keyEvent->keycode == GUI::Key::unknown)
+ {
+ keyEvent->keycode = GUI::Key::character;
+ }
+
+ keyEvent->text = str; // TODO: UTF8 decode
+ keyEvent->direction = GUI::Direction::up;
+
+ native->pushBackEvent(keyEvent);
+ [super keyUp: event];
+}
+
+- (void) dealloc
+{
+ [super dealloc];
+}
+
+- (void)bindNative:(GUI::NativeWindowCocoa*)_native
+{
+ native = _native;
+}
+
+- (void) unbindNative
+{
+ native = nullptr;
+}
+@end
+
+
+namespace GUI
+{
+
+struct priv
+{
+ NSWindow* window;
+ DGView* view;
+ id listener;
+ id parent_view;
+ std::uint8_t* pixel_buffer{nullptr};
+ std::size_t pixel_buffer_width{0};
+ std::size_t pixel_buffer_height{0};
+};
+
+NativeWindowCocoa::NativeWindowCocoa(void* native_window, Window& window)
+ : window(window)
+ , priv(new struct priv())
+ , native_window(native_window)
+{
+ [NSAutoreleasePool new];
+ [NSApplication sharedApplication];
+ [NSApp setActivationPolicy:NSApplicationActivationPolicyRegular];
+
+ priv->view = [DGView new];
+
+ [priv->view bindNative:this];
+
+ if(native_window)
+ {
+ if(sizeof(std::size_t) == sizeof(unsigned int)) // 32 bit machine
+ {
+ WindowRef ptr = (WindowRef)native_window;
+ priv->window = [[[NSWindow alloc] initWithWindowRef:ptr] retain];
+ priv->parent_view = [priv->window contentView];
+ }
+ else // 64 bit machine
+ {
+ priv->parent_view = (NSView*)native_window;
+ priv->window = [priv->parent_view window];
+ }
+
+ [priv->parent_view addSubview:priv->view];
+ [priv->view display];
+ [priv->parent_view setNeedsDisplay:YES];
+ }
+ else
+ {
+ priv->window =
+ [[[NSWindow alloc] initWithContentRect:NSMakeRect(0, 0, 10, 10)
+ styleMask:STYLE_MASK
+ backing:NSBackingStoreBuffered
+ defer:NO]
+ retain];
+ [priv->window setLevel:NSStatusWindowLevel];
+ }
+
+ priv->listener =
+ [[[DGListener alloc] initWithWindow:priv->window
+ native:this]
+ retain];
+
+ if(native_window)
+ {
+ [[priv->window contentView] addSubview:priv->view];
+ }
+ else
+ {
+ [priv->window setReleasedWhenClosed:NO];
+ [priv->window setContentView:priv->view];
+ }
+
+ scale = [[NSScreen mainScreen] backingScaleFactor];
+
+ [priv->view setWantsLayer:YES];
+ [priv->view setLayerContentsPlacement:NSViewLayerContentsPlacementTopLeft];
+ [priv->view updateTrackingAreas];
+
+ if(!native_window)
+ {
+ hide();
+ }
+}
+
+NativeWindowCocoa::~NativeWindowCocoa()
+{
+ // Make the garbage collector able to collect the ObjC objects:
+ if(visible())
+ {
+ hide();
+ }
+
+ [priv->listener unbindNative];
+ [priv->listener release];
+
+ [priv->view unbindNative];
+ [priv->view release];
+
+ if(native_window)
+ {
+ if(sizeof(std::size_t) == sizeof(unsigned int)) // 32 bit machine
+ {
+ [priv->window release];
+ }
+ else
+ {
+ // in 64-bit the window was not created by us
+ }
+ }
+ else
+ {
+ [priv->window release];
+ }
+}
+
+void NativeWindowCocoa::setFixedSize(std::size_t width, std::size_t height)
+{
+ resize(width, height);
+ [priv->window setMinSize:NSMakeSize(width, height + 22)];
+ [priv->window setMaxSize:NSMakeSize(width, height + 22)];
+}
+
+void NativeWindowCocoa::setAlwaysOnTop(bool always_on_top)
+{
+ if(always_on_top)
+ {
+ [priv->window setLevel: NSStatusWindowLevel];
+ }
+ else
+ {
+ [priv->window setLevel: NSNormalWindowLevel];
+ }
+}
+
+void NativeWindowCocoa::resize(std::size_t width, std::size_t height)
+{
+ [priv->window setContentSize:NSMakeSize(width, height)];
+}
+
+std::pair<std::size_t, std::size_t> NativeWindowCocoa::getSize() const
+{
+ if(native_window)
+ {
+ auto frame = [priv->parent_view frame];
+ return {frame.size.width, frame.size.height - frame.origin.y};
+ }
+ else
+ {
+ NSSize size = [priv->view frame].size;
+ return {size.width, size.height};
+ }
+}
+
+void NativeWindowCocoa::move(int x, int y)
+{
+ NSRect screen = [[NSScreen mainScreen] frame];
+ [priv->window setFrameTopLeftPoint:NSMakePoint(x, screen.size.height - y)];
+}
+
+std::pair<int, int> NativeWindowCocoa::getPosition() const
+{
+ NSRect screen = [[NSScreen mainScreen] frame];
+ NSPoint pos = [[priv->window contentView] frame].origin;
+ return {pos.x, screen.size.height - pos.y};
+}
+
+void NativeWindowCocoa::show()
+{
+ if(!native_window)
+ {
+ [priv->window makeKeyAndOrderFront:priv->window];
+ [NSApp activateIgnoringOtherApps:YES];
+ }
+}
+
+void NativeWindowCocoa::hide()
+{
+ if(!native_window)
+ {
+ [priv->window orderOut:priv->window];
+ }
+}
+
+bool NativeWindowCocoa::visible() const
+{
+ return [priv->window isVisible];
+}
+
+void NativeWindowCocoa::redraw(const Rect& dirty_rect)
+{
+ NSSize size;
+ if(native_window)
+ {
+ size = [priv->parent_view frame].size;
+ }
+ else
+ {
+ size = [priv->view frame].size;
+ }
+
+ std::size_t width = size.width;
+ std::size_t height = size.height;
+
+ if(priv->pixel_buffer == nullptr ||
+ priv->pixel_buffer_width != width ||
+ priv->pixel_buffer_height != height)
+ {
+ if(priv->pixel_buffer) delete[] priv->pixel_buffer;
+ priv->pixel_buffer = new std::uint8_t[width * height * 4];
+ priv->pixel_buffer_width = width;
+ priv->pixel_buffer_height = height;
+ }
+
+ CGColorSpaceRef rgb = CGColorSpaceCreateWithName(kCGColorSpaceGenericRGB);
+ CGContextRef gc =
+ CGBitmapContextCreate(priv->pixel_buffer, width, height,
+ 8, width * 4, rgb,
+ IMAGE_FLAGS);
+ CGColorSpaceRelease(rgb);
+
+ size_t pitch = CGBitmapContextGetBytesPerRow(gc);
+ uint8_t *buffer = (uint8_t *)CGBitmapContextGetData(gc);
+
+ struct Pixel
+ {
+ std::uint8_t red;
+ std::uint8_t green;
+ std::uint8_t blue;
+ std::uint8_t alpha;
+ };
+ std::uint8_t* pixels = window.wpixbuf.buf;
+ for(std::size_t y = dirty_rect.y1; y < std::min(dirty_rect.y2, height); ++y)
+ {
+ Pixel *row = (Pixel *)(buffer + y * pitch);
+ for(std::size_t x = dirty_rect.x1; x < std::min(dirty_rect.x2, width); ++x)
+ {
+ row[x] = *(Pixel*)&pixels[(y * width + x) * 3];
+ row[x].alpha = 0xff;
+ }
+ }
+ CGImageRef image = CGBitmapContextCreateImage(gc);
+ CGContextRelease(gc);
+
+ auto nsImage = [[NSImage alloc] initWithCGImage:image size:NSZeroSize];
+
+ id layerContents = [nsImage layerContentsForContentsScale:scale];
+ [[priv->view layer] setContents:layerContents];
+ updateLayerOffset();
+ [[priv->view layer] setContentsScale:scale];
+}
+
+void NativeWindowCocoa::setCaption(const std::string &caption)
+{
+ NSString* title =
+ [NSString stringWithCString:caption.data()
+ encoding:[NSString defaultCStringEncoding]];
+ [priv->window setTitle:title];
+}
+
+void NativeWindowCocoa::grabMouse(bool grab)
+{
+}
+
+void NativeWindowCocoa::updateLayerOffset()
+{
+ if(native_window)
+ {
+ //auto r1 = [priv->parent_view frame];
+ auto r2 = [priv->view frame];
+
+ CATransform3D t = [[priv->view layer] transform];
+ if(t.m42 != -r2.origin.y)
+ {
+ t.m42 = -r2.origin.y; // y
+ [[priv->view layer] setTransform:t];
+ }
+ }
+}
+
+EventQueue NativeWindowCocoa::getEvents()
+{
+ if(first)
+ {
+ resized();
+ first = false;
+ }
+
+ // If this is the root window, process the events - event processing will
+ // be handled by the hosting window if the window is embedded.
+ if(!native_window)
+ {
+ NSEvent* event = nil;
+ do
+ {
+ event = [NSApp nextEventMatchingMask:EVENT_MASK
+ untilDate:[NSDate distantPast]
+ inMode:NSDefaultRunLoopMode
+ dequeue:YES];
+ [NSApp sendEvent:event];
+ }
+ while(event != nil);
+ }
+
+ EventQueue events;
+ std::swap(events, event_queue);
+ return events;
+}
+
+void* NativeWindowCocoa::getNativeWindowHandle() const
+{
+ if(sizeof(std::size_t) == sizeof(unsigned int)) // 32 bit machine
+ {
+ return [priv->window windowRef];
+ }
+ else // 64 bit machine
+ {
+ return [priv->window contentView];
+ }
+}
+
+Point NativeWindowCocoa::translateToScreen(const Point& point)
+{
+ NSRect e = [[NSScreen mainScreen] frame];
+ NSRect frame;
+ if(native_window)
+ {
+ frame = [priv->parent_view frame];
+ }
+ else
+ {
+ frame = [priv->view frame];
+ }
+
+ NSRect rect { { point.x + frame.origin.x,
+ frame.size.height - point.y + frame.origin.y},
+ {0.0, 0.0} };
+ rect = [priv->window convertRectToScreen:rect];
+
+ return { (int)rect.origin.x, (int)(e.size.height - rect.origin.y) };
+}
+
+Window& NativeWindowCocoa::getWindow()
+{
+ return window;
+}
+
+PixelBuffer& NativeWindowCocoa::getWindowPixbuf()
+{
+ window.updateBuffer();
+ return window.wpixbuf;
+}
+
+void NativeWindowCocoa::resized()
+{
+ if(native_window)
+ {
+ NSRect frame = [priv->parent_view frame];
+ [priv->view setFrame:frame];
+ [priv->view updateTrackingAreas];
+ updateLayerOffset();
+ }
+
+ auto resizeEvent = std::make_shared<GUI::ResizeEvent>();
+ resizeEvent->width = 42; // size is not actually used
+ resizeEvent->height = 42; // size is not actually used
+ pushBackEvent(resizeEvent);
+}
+
+void NativeWindowCocoa::pushBackEvent(std::shared_ptr<Event> event)
+{
+ event_queue.push_back(event);
+}
+
+} // GUI::
diff --git a/dggui/nativewindow_pugl.cc b/dggui/nativewindow_pugl.cc
new file mode 100644
index 0000000..f94b82b
--- /dev/null
+++ b/dggui/nativewindow_pugl.cc
@@ -0,0 +1,382 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/***************************************************************************
+ * nativewindow_pugl.cc
+ *
+ * Fri Dec 28 18:45:57 CET 2012
+ * Copyright 2012 Bent Bisballe Nyeng
+ * deva@aasimon.org
+ ****************************************************************************/
+
+/*
+ * This file is part of DrumGizmo.
+ *
+ * DrumGizmo is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * DrumGizmo is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with DrumGizmo; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+#include "nativewindow_pugl.h"
+
+#include <stdlib.h>
+#include <list>
+
+#ifdef __APPLE__
+#include <OpenGL/glu.h>
+#else
+#include <GL/glu.h>
+#include <GL/glext.h>
+#include <GL/gl.h>
+#endif
+
+#include "window.h"
+#include "guievent.h"
+
+#include <hugin.hpp>
+
+namespace GUI
+{
+
+NativeWindowPugl::NativeWindowPugl(void* native_window, Window& window)
+ : window(window)
+{
+ INFO(nativewindow, "Running with PuGL native window\n");
+ view = puglInit(nullptr, nullptr);
+ puglInitContextType(view, PUGL_GL);
+ if(native_window)
+ {
+ puglInitWindowParent(view, (PuglNativeWindow)native_window);
+ }
+ puglInitWindowClass(view, "DrumgGizmo");
+ puglInitWindowSize(view, 750, 466);
+ puglInitResizable(view, true);
+ puglCreateWindow(view, "DrumGizmo");
+
+ puglSetHandle(view, (PuglHandle)this);
+ puglSetEventFunc(view, onEvent);
+}
+
+NativeWindowPugl::~NativeWindowPugl()
+{
+ puglDestroy(view);
+}
+
+void NativeWindowPugl::setFixedSize(std::size_t width, std::size_t height)
+{
+// redraw();
+}
+
+void NativeWindowPugl::resize(std::size_t width, std::size_t height)
+{
+// DEBUG(nativewindow_pugl, "Resizing to %dx%d\n", width, height);
+// init();
+// redraw();
+}
+
+std::pair<std::size_t, std::size_t> NativeWindowPugl::getSize() const
+{
+ int width, height;
+ puglGetSize(view, &width, &height);
+ return {width, height};
+}
+
+void NativeWindowPugl::move(int x, int y)
+{
+// redraw();
+}
+
+void NativeWindowPugl::show()
+{
+ puglShowWindow(view);
+}
+
+void NativeWindowPugl::hide()
+{
+ puglHideWindow(view);
+}
+
+bool NativeWindowPugl::visible() const
+{
+ return puglGetVisible(view);
+}
+
+void NativeWindowPugl::redraw(const Rect& dirty_rect)
+{
+ //puglPostRedisplay(view);// handleBuffer();
+ onDisplay(view);
+}
+
+void NativeWindowPugl::setCaption(const std::string &caption)
+{
+// redraw();
+}
+
+void NativeWindowPugl::grabMouse(bool grab)
+{
+ puglGrabFocus(view);
+}
+
+EventQueue NativeWindowPugl::getEvents()
+{
+ puglProcessEvents(view);
+ EventQueue events;
+ std::swap(events, event_queue);
+ return events;
+}
+
+void* NativeWindowPugl::getNativeWindowHandle() const
+{
+ return (void*)puglGetNativeWindow(view);
+}
+
+void NativeWindowPugl::onEvent(PuglView* view, const PuglEvent* event)
+{
+ NativeWindowPugl* native = (NativeWindowPugl*)puglGetHandle(view);
+
+ switch(event->type)
+ {
+ case PUGL_NOTHING:
+ break;
+ case PUGL_CONFIGURE:
+ onReshape(view, event->configure.width, event->configure.height);
+ {
+ auto resize_event = std::make_shared<ResizeEvent>();
+ resize_event->width = event->configure.width;
+ resize_event->height = event->configure.height;
+ native->event_queue.push_back(resize_event);
+ }
+ break;
+ case PUGL_EXPOSE:
+ onDisplay(view);
+ break;
+ case PUGL_CLOSE:
+ //quit = 1;
+ break;
+ case PUGL_KEY_PRESS:
+ fprintf(stderr, "Key %u (char %u) press (%s)%s\n",
+ event->key.keycode, event->key.character, event->key.utf8,
+ event->key.filter ? " (filtered)" : "");
+ if (event->key.character == 'q' ||
+ event->key.character == 'Q' ||
+ event->key.character == PUGL_CHAR_ESCAPE) {
+ //quit = 1;
+ }
+ break;
+ case PUGL_KEY_RELEASE:
+ fprintf(stderr, "Key %u (char %u) release (%s)%s\n",
+ event->key.keycode, event->key.character, event->key.utf8,
+ event->key.filter ? " (filtered)" : "");
+ break;
+ case PUGL_MOTION_NOTIFY:
+ {
+ auto mouseMoveEvent = std::make_shared<MouseMoveEvent>();
+ mouseMoveEvent->x = event->motion.x;
+ mouseMoveEvent->y = event->motion.y;
+ native->event_queue.push_back(mouseMoveEvent);
+ }
+ break;
+ case PUGL_BUTTON_PRESS:
+ case PUGL_BUTTON_RELEASE:
+ {
+ auto buttonEvent = std::make_shared<ButtonEvent>();
+ buttonEvent->x = event->button.x;
+ buttonEvent->y = event->button.y;
+ switch(event->button.button) {
+ case 1:
+ buttonEvent->button = MouseButton::left;
+ break;
+ case 2:
+ buttonEvent->button = MouseButton::middle;
+ break;
+ case 3:
+ buttonEvent->button = MouseButton::right;
+ break;
+ default:
+ WARN(X11, "Unknown button %d, setting to MouseButton::left\n",
+ event->button.button);
+ buttonEvent->button = MouseButton::left;
+ break;
+ }
+
+ buttonEvent->direction =
+ (event->type == PUGL_BUTTON_PRESS) ?
+ Direction::down : Direction::up;
+
+ buttonEvent->doubleClick =
+ (event->type == PUGL_BUTTON_PRESS) &&
+ ((event->button.time - native->last_click) < 200);
+
+ if(event->type == PUGL_BUTTON_PRESS)
+ {
+ native->last_click = event->button.time;
+ }
+ native->event_queue.push_back(buttonEvent);
+ }
+ fprintf(stderr, "Mouse %d %s at %f,%f ",
+ event->button.button,
+ (event->type == PUGL_BUTTON_PRESS) ? "down" : "up",
+ event->button.x,
+ event->button.y);
+ ///printModifiers(view, event->scroll.state);
+ break;
+ case PUGL_SCROLL:
+ {
+ auto scrollEvent = std::make_shared<ScrollEvent>();
+ scrollEvent->x = event->scroll.x;
+ scrollEvent->y = event->scroll.y;
+ scrollEvent->delta = event->scroll.dy * -1;//scroll * ((xevent.xbutton.button == 4) ? -1 : 1);
+ native->event_queue.push_back(scrollEvent);
+ }
+ fprintf(stderr, "Scroll %f %f %f %f ",
+ event->scroll.x, event->scroll.y, event->scroll.dx, event->scroll.dy);
+ //printModifiers(view, event->scroll.state);
+ //dist += event->scroll.dy;
+ //if (dist < 10.0f) {
+ // dist = 10.0f;
+ //}
+ puglPostRedisplay(view);
+ break;
+ case PUGL_ENTER_NOTIFY:
+ fprintf(stderr, "Entered\n");
+ break;
+ case PUGL_LEAVE_NOTIFY:
+ fprintf(stderr, "Exited\n");
+ break;
+ case PUGL_FOCUS_IN:
+ fprintf(stderr, "Focus in\n");
+ break;
+ case PUGL_FOCUS_OUT:
+ fprintf(stderr, "Focus out\n");
+ break;
+ }
+}
+
+void NativeWindowPugl::onReshape(PuglView* view, int width, int height)
+{
+ glMatrixMode(GL_PROJECTION);
+ glLoadIdentity();
+ glViewport(0, 0, width, height);
+}
+
+void NativeWindowPugl::onDisplay(PuglView* view)
+{
+ NativeWindowPugl* native = (NativeWindowPugl*)puglGetHandle(view);
+ Window& window = native->window;
+ //window.redraw();
+
+ if((window.wpixbuf.width < 16) || (window.wpixbuf.height < 16))
+ {
+ return;
+ }
+
+ puglEnterContext(view);
+
+ glDisable(GL_DEPTH_TEST);
+ glClear(GL_COLOR_BUFFER_BIT);
+
+ glMatrixMode(GL_MODELVIEW);
+ glLoadIdentity();
+ glTranslatef(0.0f, 0.0f, 0.0f);
+
+ GLuint image;
+
+ glGenTextures(1, &image);
+
+ //glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
+ //glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
+
+ glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+ glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+ glTexEnvi(GL_TEXTURE_ENV,GL_TEXTURE_ENV_MODE, GL_REPLACE);
+
+ glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
+ glTexImage2D(GL_TEXTURE_2D,
+ 0, GL_RGBA,
+ window.wpixbuf.width,
+ window.wpixbuf.height,
+ 0, GL_RGB, GL_UNSIGNED_BYTE,
+ window.wpixbuf.buf);
+
+ glEnable(GL_TEXTURE_2D);
+
+ glBegin(GL_QUADS);
+ glTexCoord2d(0.0f, 0.0f); glVertex2f(-1.0f, -1.0f);
+ glTexCoord2d(0.0f, -1.0f); glVertex2f(-1.0f, 1.0f);
+ glTexCoord2d(1.0f, -1.0f); glVertex2f( 1.0f, 1.0f);
+ glTexCoord2d(1.0f, 0.0f); glVertex2f( 1.0f, -1.0f);
+ glEnd();
+
+ glDeleteTextures(1, &image);
+ glDisable(GL_TEXTURE_2D);
+ glFlush();
+
+ puglLeaveContext(view, true);
+}
+
+void NativeWindowPugl::onMouse(PuglView* view, int button, bool press, int x, int y)
+{
+ NativeWindowPugl* native = (NativeWindowPugl*)puglGetHandle(view);
+
+ DEBUG(nativewindow_pugl, "Mouse %d %s at (%d,%d)\n", button,
+ press? "down":"up", x, y);
+
+ ButtonEvent* e = new ButtonEvent();
+ e->x = x;
+ e->y = y;
+
+ switch(button) {
+ case 1:
+ e->button = MouseButton::left;
+ break;
+ case 2:
+ e->button = MouseButton::middle;
+ break;
+ case 3:
+ default:
+ e->button = MouseButton::right;
+ break;
+ }
+
+ e->direction = press ? Direction::down : Direction::up;
+ e->doubleClick = false;
+
+ native->eventq.push_back(e);
+}
+
+void NativeWindowPugl::onKeyboard(PuglView* view, bool press, uint32_t key)
+{
+ NativeWindowPugl* native = (NativeWindowPugl*)puglGetHandle(view);
+
+ KeyEvent* e = new KeyEvent();
+ e->direction = press ? Direction::down : Direction::up;
+
+ switch(key)
+ {
+ case PUGL_KEY_LEFT: e->keycode = Key::left; break;
+ case PUGL_KEY_RIGHT: e->keycode = Key::right; break;
+ case PUGL_KEY_UP: e->keycode = Key::up; break;
+ case PUGL_KEY_DOWN: e->keycode = Key::down; break;
+ case PUGL_KEY_PAGE_UP: e->keycode = Key::pageDown; break;
+ case PUGL_KEY_PAGE_DOWN: e->keycode = Key::pageUp; break;
+ default: e->keycode = Key::unknown; break;
+ }
+
+ // TODO: perform character type check
+ if(e->keycode == Key::unknown)
+ {
+ e->keycode = Key::character;
+ e->text.assign(1, (char)key);
+ }
+
+ native->eventq.push_back(e);
+}
+
+} // GUI::
diff --git a/dggui/nativewindow_pugl.h b/dggui/nativewindow_pugl.h
new file mode 100644
index 0000000..6a667f4
--- /dev/null
+++ b/dggui/nativewindow_pugl.h
@@ -0,0 +1,83 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/***************************************************************************
+ * nativewindow_pugl.h
+ *
+ * Fri Dec 28 18:45:56 CET 2012
+ * Copyright 2012 Bent Bisballe Nyeng
+ * deva@aasimon.org
+ ****************************************************************************/
+
+/*
+ * This file is part of DrumGizmo.
+ *
+ * DrumGizmo is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * DrumGizmo is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with DrumGizmo; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+#pragma once
+
+#include "nativewindow.h"
+extern "C"
+{
+#include <pugl/pugl.h>
+}
+
+#include <list>
+
+namespace GUI
+{
+
+class Event;
+class Window;
+
+class NativeWindowPugl : public NativeWindow {
+public:
+ NativeWindowPugl(void* native_window, Window& window);
+ ~NativeWindowPugl();
+
+ void setFixedSize(std::size_t width, std::size_t height) override;
+ void resize(std::size_t width, std::size_t height) override;
+ std::pair<std::size_t, std::size_t> getSize() const override;
+
+ void move(int x, int y) override;
+ std::pair<int, int> getPosition() const override{ return {}; }
+
+ void show() override;
+ void setCaption(const std::string &caption) override;
+ void hide() override;
+ bool visible() const override;
+ void redraw(const Rect& dirty_rect) override;
+ void grabMouse(bool grab) override;
+
+ EventQueue getEvents() override;
+
+ void* getNativeWindowHandle() const override;
+
+private:
+ Window& window;
+ PuglView* view{nullptr};
+
+ std::list<Event*> eventq;
+
+ // Internal pugl c-callbacks
+ static void onEvent(PuglView* view, const PuglEvent* event);
+ static void onReshape(PuglView* view, int width, int height);
+ static void onDisplay(PuglView* view);
+ static void onMouse(PuglView* view, int button, bool press, int x, int y);
+ static void onKeyboard(PuglView* view, bool press, uint32_t key);
+
+ EventQueue event_queue;
+ std::uint32_t last_click{0};
+};
+
+} // GUI::
diff --git a/dggui/nativewindow_win32.cc b/dggui/nativewindow_win32.cc
new file mode 100644
index 0000000..4b31130
--- /dev/null
+++ b/dggui/nativewindow_win32.cc
@@ -0,0 +1,584 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/***************************************************************************
+ * nativewindow_win32.cc
+ *
+ * Fri Dec 28 18:45:52 CET 2012
+ * Copyright 2012 Bent Bisballe Nyeng
+ * deva@aasimon.org
+ ****************************************************************************/
+
+/*
+ * This file is part of DrumGizmo.
+ *
+ * DrumGizmo is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * DrumGizmo is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with DrumGizmo; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+#include "nativewindow_win32.h"
+
+#include <cstring>
+#include <algorithm>
+#include <commctrl.h>
+
+#include "window.h"
+
+namespace GUI
+{
+
+static BOOL trackMouse(HWND hwnd)
+{
+ TRACKMOUSEEVENT ev{};
+ ev.cbSize = sizeof(ev);
+ ev.dwFlags = TME_HOVER | TME_LEAVE;
+ ev.hwndTrack = hwnd;
+ ev.dwHoverTime = 1;
+ return TrackMouseEvent(&ev);
+}
+
+LRESULT CALLBACK NativeWindowWin32::dialogProc(HWND hwnd, UINT msg,
+ WPARAM wp, LPARAM lp)
+{
+ NativeWindowWin32* native =
+ (NativeWindowWin32*)GetWindowLongPtr(hwnd, GWLP_USERDATA);
+
+ // NOTE: 'native' is nullptr intil the WM_CREATE message has been handled.
+ if(!native)
+ {
+ return DefWindowProc(hwnd, msg, wp, lp);
+ }
+
+ Window& window = native->window;
+
+ switch(msg)
+ {
+ case WM_SIZE:
+ if(wp > 4)
+ {
+ // Bogus value - ignore
+ break;
+ }
+ {
+ auto resizeEvent = std::make_shared<ResizeEvent>();
+ resizeEvent->width = LOWORD(lp);
+ resizeEvent->height = HIWORD(lp);
+ native->event_queue.push_back(resizeEvent);
+ }
+ break;
+
+ case WM_MOVE:
+ {
+ auto moveEvent = std::make_shared<MoveEvent>();
+ moveEvent->x = (short)LOWORD(lp);
+ moveEvent->y = (short)HIWORD(lp);
+ native->event_queue.push_back(moveEvent);
+ }
+ break;
+
+ case WM_CLOSE:
+ {
+ auto closeEvent = std::make_shared<CloseEvent>();
+ native->event_queue.push_back(closeEvent);
+ }
+ return 0; // Do not call DefWindowProc for this event.
+// HWND child, old;
+// old = 0;
+
+// numDialogs--;
+
+// while(old != (child = GetNextDlgGroupItem(hwnd, hwnd, false))) {
+// old = child;
+// EndDialog(child, 0);
+// }
+
+// if(numDialogs) EndDialog(hwnd, 0);
+// else PostQuitMessage(0);
+// return 0;
+ case WM_MOUSEMOVE:
+ {
+ trackMouse(native->m_hwnd);
+ auto mouseMoveEvent = std::make_shared<MouseMoveEvent>();
+ mouseMoveEvent->x = (short)LOWORD(lp);
+ mouseMoveEvent->y = (short)HIWORD(lp);
+ native->last_mouse_position = { mouseMoveEvent->x, mouseMoveEvent->y };
+
+ if(!native->mouse_in_window)
+ {
+ auto enterEvent = std::make_shared<MouseEnterEvent>();
+ enterEvent->x = native->last_mouse_position.first;
+ enterEvent->y = native->last_mouse_position.second;
+ native->event_queue.push_back(enterEvent);
+ native->mouse_in_window = true;
+ }
+ native->event_queue.push_back(mouseMoveEvent);
+ }
+ break;
+
+ case WM_MOUSEWHEEL:
+ {
+ auto scrollEvent = std::make_shared<ScrollEvent>();
+
+ // NOTE: lp is coordinates in screen space, not client space.
+ POINT p;
+ p.x = (short)LOWORD(lp);
+ p.y = (short)HIWORD(lp);
+ ScreenToClient(hwnd, &p);
+
+ scrollEvent->x = p.x;
+ scrollEvent->y = p.y;
+ scrollEvent->delta = -1 * (short)HIWORD(wp) / 60.0f;
+ native->event_queue.push_back(scrollEvent);
+ }
+ break;
+
+ case WM_LBUTTONUP:
+ case WM_LBUTTONDBLCLK:
+ case WM_LBUTTONDOWN:
+ case WM_RBUTTONUP:
+ case WM_RBUTTONDBLCLK:
+ case WM_RBUTTONDOWN:
+ case WM_MBUTTONUP:
+ case WM_MBUTTONDBLCLK:
+ case WM_MBUTTONDOWN:
+ {
+ auto buttonEvent = std::make_shared<ButtonEvent>();
+
+ buttonEvent->x = (short)LOWORD(lp);
+ buttonEvent->y = (short)HIWORD(lp);
+
+ if(msg == WM_LBUTTONUP ||
+ msg == WM_LBUTTONDBLCLK ||
+ msg == WM_LBUTTONDOWN)
+ {
+ buttonEvent->button = MouseButton::left;
+ }
+ else if(msg == WM_MBUTTONUP ||
+ msg == WM_MBUTTONDBLCLK ||
+ msg == WM_MBUTTONDOWN)
+ {
+ buttonEvent->button = MouseButton::middle;
+ }
+ else if(msg == WM_RBUTTONUP ||
+ msg == WM_RBUTTONDBLCLK ||
+ msg == WM_RBUTTONDOWN)
+ {
+ buttonEvent->button = MouseButton::right;
+ }
+ else
+ {
+ break; // unknown button
+ }
+
+ // Double-clicking the a mouse button actually generates a sequence
+ // of four messages: WM_xBUTTONDOWN, WM_xBUTTONUP, WM_xBUTTONDBLCLK, and
+ // WM_xBUTTONUP. In other words the second WM_xBUTTONDOWN is replaced by a
+ // WM_xBUTTONDBLCLK. We simply 'return it' as a WM_xBUTTONDOWN but set the
+ // doubleClick boolean hint accordingly.
+ if(msg == WM_LBUTTONUP ||
+ msg == WM_RBUTTONUP ||
+ msg == WM_MBUTTONUP)
+ {
+ buttonEvent->direction = Direction::up;
+ }
+ else if(msg == WM_LBUTTONDOWN ||
+ msg == WM_RBUTTONDOWN ||
+ msg == WM_MBUTTONDOWN ||
+ msg == WM_LBUTTONDBLCLK ||
+ msg == WM_RBUTTONDBLCLK ||
+ msg == WM_MBUTTONDBLCLK)
+ {
+ buttonEvent->direction = Direction::down;
+ }
+
+ buttonEvent->doubleClick = (msg == WM_LBUTTONDBLCLK ||
+ msg == WM_RBUTTONDBLCLK ||
+ msg == WM_MBUTTONDBLCLK);
+
+ native->event_queue.push_back(buttonEvent);
+ }
+ break;
+
+ case WM_KEYDOWN:
+ case WM_KEYUP:
+ {
+ auto keyEvent = std::make_shared<KeyEvent>();
+
+ switch(wp) {
+ case VK_LEFT: keyEvent->keycode = Key::left; break;
+ case VK_RIGHT: keyEvent->keycode = Key::right; break;
+ case VK_UP: keyEvent->keycode = Key::up; break;
+ case VK_DOWN: keyEvent->keycode = Key::down; break;
+ case VK_BACK: keyEvent->keycode = Key::backspace; break;
+ case VK_DELETE: keyEvent->keycode = Key::deleteKey; break;
+ case VK_HOME: keyEvent->keycode = Key::home; break;
+ case VK_END: keyEvent->keycode = Key::end; break;
+ case VK_PRIOR: keyEvent->keycode = Key::pageUp; break;
+ case VK_NEXT: keyEvent->keycode = Key::pageDown; break;
+ case VK_RETURN: keyEvent->keycode = Key::enter; break;
+ default: keyEvent->keycode = Key::unknown; break;
+ }
+
+ keyEvent->text = "";
+ keyEvent->direction =
+ (msg == WM_KEYDOWN) ? Direction::down : Direction::up;
+
+ native->event_queue.push_back(keyEvent);
+ }
+ break;
+
+ case WM_CHAR:
+ {
+ if(wp >= ' ') // Filter control chars.
+ {
+ auto keyEvent = std::make_shared<KeyEvent>();
+ keyEvent->keycode = Key::character;
+ keyEvent->text += (char)wp;
+ keyEvent->direction = Direction::up;
+ native->event_queue.push_back(keyEvent);
+ }
+ }
+ break;
+
+ case WM_PAINT:
+ {
+ RECT rect;
+ GetUpdateRect(hwnd, &rect, FALSE);
+
+ // Bypass partial update, which is apparrently broken.
+ rect.left = 0;
+ rect.top = 0;
+ rect.right = window.wpixbuf.width;
+ rect.bottom = window.wpixbuf.height;
+
+ auto repaintEvent = std::make_shared<RepaintEvent>();
+ repaintEvent->x = rect.left;
+ repaintEvent->y = rect.top;
+ repaintEvent->width = rect.right - rect.left;
+ repaintEvent->height = rect.bottom - rect.top;
+ native->event_queue.push_back(repaintEvent);
+
+ // Move to window.h (in class)
+ HDC pDC;
+ HBITMAP old;
+ HBITMAP ourbitmap;
+ int* framebuf;
+ PixelBuffer& px = window.wpixbuf;
+
+ { // Create bitmap
+ HDC hDC;
+ BITMAPINFO bitmapinfo;
+ hDC = CreateCompatibleDC(nullptr);
+ bitmapinfo.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
+ bitmapinfo.bmiHeader.biWidth = px.width;
+ bitmapinfo.bmiHeader.biHeight = -px.height; // top-down
+ bitmapinfo.bmiHeader.biPlanes = 1;
+ bitmapinfo.bmiHeader.biBitCount = 32;
+ bitmapinfo.bmiHeader.biCompression = BI_RGB;
+ bitmapinfo.bmiHeader.biSizeImage = 0;
+ bitmapinfo.bmiHeader.biClrUsed = 256;
+ bitmapinfo.bmiHeader.biClrImportant = 256;
+ ourbitmap = CreateDIBSection(hDC, &bitmapinfo,
+ DIB_RGB_COLORS, (void**)&framebuf, 0, 0);
+ pDC=CreateCompatibleDC(nullptr);
+ old = (HBITMAP__*)SelectObject(pDC, ourbitmap);
+ DeleteDC(hDC);
+ }
+
+ int from_x = rect.left;
+ int to_x = std::min(rect.right, (long)px.width);
+ int from_y = rect.top;
+ int to_y = std::min(rect.bottom, (long)px.height);
+ { // Copy PixelBuffer to framebuffer
+ int idx = 0;
+ for(int y = from_y; y < to_y; ++y)
+ {
+ for(int x = from_x; x < to_x; ++x)
+ {
+ *(framebuf + idx) = RGB(px.buf[(x + y * px.width) * 3 + 2],
+ px.buf[(x + y * px.width) * 3 + 1],
+ px.buf[(x + y * px.width) * 3 + 0]);
+ ++idx;
+ }
+ }
+ }
+
+ PAINTSTRUCT ps;
+ HDC hdc = BeginPaint(native->m_hwnd, &ps);
+ BitBlt(hdc, from_x, from_y, to_x, to_y, pDC, from_x, from_y, SRCCOPY);
+ EndPaint(native->m_hwnd, &ps);
+
+ { // Destroy bitmap (move to window.cc)
+ SelectObject(pDC,old);
+ DeleteDC(pDC);
+ DeleteObject(ourbitmap);
+ }
+ }
+ break;
+
+ case WM_MOUSELEAVE:
+ {
+ auto leaveEvent = std::make_shared<MouseLeaveEvent>();
+ leaveEvent->x = native->last_mouse_position.first;
+ leaveEvent->y = native->last_mouse_position.second;
+ native->event_queue.push_back(leaveEvent);
+ native->mouse_in_window = false;
+ }
+ break;
+ }
+
+ return DefWindowProc(hwnd, msg, wp, lp);
+}
+
+LRESULT CALLBACK NativeWindowWin32::subClassProc(HWND hwnd, UINT msg,
+ WPARAM wp, LPARAM lp,
+ UINT_PTR id, DWORD_PTR data)
+{
+ NativeWindowWin32* native = (NativeWindowWin32*)data;
+
+ // NOTE: 'native' is nullptr intil the WM_CREATE message has been handled.
+ if(!native)
+ {
+ return DefWindowProc(hwnd, msg, wp, lp);
+ }
+
+ switch(msg)
+ {
+ case WM_SIZE:
+ if(wp > 4)
+ {
+ // Bogus value - ignore
+ break;
+ }
+ {
+ // Parent window size changed, replicate this size in inner window.
+ int width = LOWORD(lp);
+ int height = HIWORD(lp);
+ SetWindowPos(native->m_hwnd, nullptr, -1, -1, width, height, SWP_NOMOVE);
+ }
+ break;
+ }
+
+ return DefSubclassProc(hwnd, msg, wp, lp);
+}
+
+NativeWindowWin32::NativeWindowWin32(void* native_window, Window& window)
+ : window(window)
+{
+ WNDCLASSEX wcex{};
+
+ //Time to register a window class.
+ //Generic flags and everything. cbWndExtra is the size of a pointer to an
+ // object - we need this in the wndproc handler.
+
+ wcex.cbSize = sizeof(WNDCLASSEX);
+ wcex.style = CS_DBLCLKS;//class_style;
+ wcex.lpfnWndProc = (WNDPROC)dialogProc;
+ wcex.hCursor = LoadCursor(nullptr, IDC_ARROW);
+ // Set data:
+ wcex.cbWndExtra = sizeof(NativeWindowWin32*); // Size of data.
+ wcex.hInstance = GetModuleHandle(nullptr);
+
+ // if(ex_style && WS_EX_TRANSPARENT == WS_EX_TRANSPARENT) {
+ // wcex.hbrBackground = nullptr;
+ // } else {
+ wcex.hbrBackground = nullptr;//(HBRUSH) COLOR_BACKGROUND + 1;
+ // }
+
+ const char* name = "DrumGizmoClass";
+ char* c_name = (char*)malloc(strlen(name) + 1);
+ strcpy(c_name, name);
+ wcex.lpszClassName = m_className = c_name;
+
+ RegisterClassEx(&wcex);
+
+ parent_window = (HWND)native_window;
+
+ int width = 1, height = 1;
+ if(parent_window)
+ {
+ // Listen in on parent size changes.
+ SetWindowSubclass(parent_window, subClassProc, 42, (LONG_PTR)this);
+
+ // Resize newly created window to fit into parent.
+ RECT rect;
+ GetClientRect(parent_window, &rect);
+
+ auto resizeEvent = std::make_shared<ResizeEvent>();
+ width = resizeEvent->width = rect.right - rect.left;
+ height = resizeEvent->height = rect.bottom - rect.top;
+ event_queue.push_back(resizeEvent);
+ }
+
+ m_hwnd = CreateWindowEx(0/*ex_style*/, m_className,
+ "DGBasisWidget",
+ (native_window?WS_CHILD:WS_OVERLAPPEDWINDOW) |
+ (native_window?WS_VISIBLE:0),
+ 0, 0,
+ width, height,
+ parent_window, nullptr,
+ GetModuleHandle(nullptr), nullptr);
+
+ SetWindowLongPtr(m_hwnd, GWLP_USERDATA, (LONG_PTR)this);
+
+ // Set up initial tracking of the mouse leave events
+ trackMouse(m_hwnd);
+}
+
+NativeWindowWin32::~NativeWindowWin32()
+{
+ if(parent_window)
+ {
+ RemoveWindowSubclass(parent_window, subClassProc, 42);
+ }
+ DestroyWindow(m_hwnd);
+ UnregisterClass(m_className, GetModuleHandle(nullptr));
+ free(m_className);
+}
+
+void NativeWindowWin32::setFixedSize(std::size_t width, std::size_t height)
+{
+ resize(width, height);
+ LONG style = GetWindowLong(m_hwnd, GWL_STYLE);
+ style &= ~(WS_THICKFRAME | WS_MAXIMIZEBOX);
+ SetWindowLong(m_hwnd, GWL_STYLE, style);
+}
+
+void NativeWindowWin32::setAlwaysOnTop(bool always_on_top)
+{
+ this->always_on_top = always_on_top;
+ SetWindowPos(m_hwnd, always_on_top ? HWND_TOPMOST : nullptr,
+ 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE);
+}
+
+void NativeWindowWin32::resize(std::size_t width, std::size_t height)
+{
+ auto hwnd = m_hwnd;
+ //if(parent_window)
+ //{
+ // hwnd = parent_window;
+ //}
+
+ // Set requested size on the window (or parent)
+ SetWindowPos(hwnd, always_on_top ? HWND_TOPMOST : nullptr,
+ -1, -1, (int)width, (int)height, SWP_NOMOVE);
+
+ // Ask the client window what size it actually got
+ RECT rect;
+ GetClientRect(m_hwnd, &rect);
+ int w = width - rect.right;
+ int h = height - rect.bottom;
+
+ // Set the compensated size on the window (or parent)
+ SetWindowPos(hwnd, always_on_top ? HWND_TOPMOST : nullptr,
+ -1, -1, width + w, height + h, SWP_NOMOVE);
+}
+
+std::pair<std::size_t, std::size_t> NativeWindowWin32::getSize() const
+{
+ RECT rect;
+ GetClientRect(m_hwnd, &rect);
+ return std::make_pair(rect.right - rect.left, rect.bottom - rect.top);
+}
+
+void NativeWindowWin32::move(int x, int y)
+{
+ SetWindowPos(m_hwnd, always_on_top ? HWND_TOPMOST : nullptr,
+ (int)x, (int)y, -1, -1, SWP_NOSIZE);
+}
+
+std::pair<int, int> NativeWindowWin32::getPosition() const
+{
+ RECT rect;
+ GetClientRect(m_hwnd, &rect);
+ return std::make_pair(rect.left, rect.top);
+}
+
+void NativeWindowWin32::show()
+{
+ ShowWindow(m_hwnd, SW_SHOW);
+}
+
+void NativeWindowWin32::hide()
+{
+ ShowWindow(m_hwnd, SW_HIDE);
+}
+
+bool NativeWindowWin32::visible() const
+{
+ return IsWindowVisible(m_hwnd);
+}
+
+void NativeWindowWin32::redraw(const Rect& dirty_rect)
+{
+ // Send WM_PAINT message. Buffer transfering is handled in MessageHandler.
+ if(parent_window == nullptr)
+ {
+ RECT rect =
+ {
+ (long)dirty_rect.x1,
+ (long)dirty_rect.y1,
+ (long)dirty_rect.x2,
+ (long)dirty_rect.y2
+ };
+ RedrawWindow(m_hwnd, &rect, nullptr, RDW_INVALIDATE);
+ UpdateWindow(m_hwnd);
+ }
+ else
+ {
+ InvalidateRect(m_hwnd, 0, TRUE);
+ }
+}
+
+void NativeWindowWin32::setCaption(const std::string &caption)
+{
+ SetWindowText(m_hwnd, caption.c_str());
+}
+
+void NativeWindowWin32::grabMouse(bool grab)
+{
+ if(grab)
+ {
+ SetCapture(m_hwnd);
+ }
+ else
+ {
+ ReleaseCapture();
+ }
+}
+
+EventQueue NativeWindowWin32::getEvents()
+{
+ MSG msg;
+ while(PeekMessage(&msg, m_hwnd, 0, 0, PM_REMOVE) != 0)
+ {
+ TranslateMessage(&msg);
+ DispatchMessage(&msg);
+ }
+
+ EventQueue events;
+ std::swap(events, event_queue);
+ return events;
+}
+
+void* NativeWindowWin32::getNativeWindowHandle() const
+{
+ return (void*)m_hwnd;
+}
+
+Point NativeWindowWin32::translateToScreen(const Point& point)
+{
+ POINT p{ point.x, point.y };
+ ClientToScreen(m_hwnd, &p);
+ return { p.x, p.y };
+}
+
+} // GUI::
diff --git a/dggui/nativewindow_win32.h b/dggui/nativewindow_win32.h
new file mode 100644
index 0000000..046b38a
--- /dev/null
+++ b/dggui/nativewindow_win32.h
@@ -0,0 +1,78 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/***************************************************************************
+ * nativewindow_win32.h
+ *
+ * Fri Dec 28 18:45:51 CET 2012
+ * Copyright 2012 Bent Bisballe Nyeng
+ * deva@aasimon.org
+ ****************************************************************************/
+
+/*
+ * This file is part of DrumGizmo.
+ *
+ * DrumGizmo is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * DrumGizmo is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with DrumGizmo; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+#pragma once
+
+#include <queue>
+
+#include "nativewindow.h"
+
+#define WIN32_LEAN_AND_MEAN
+#include <windows.h>
+typedef HWND WNDID;
+
+namespace GUI {
+
+class Window;
+class Event;
+
+class NativeWindowWin32 : public NativeWindow {
+public:
+ NativeWindowWin32(void* native_window, Window& window);
+ ~NativeWindowWin32();
+
+ void setFixedSize(std::size_t width, std::size_t height) override;
+ void setAlwaysOnTop(bool always_on_top) override;
+ void resize(std::size_t width, std::size_t height) override;
+ std::pair<std::size_t, std::size_t> getSize() const override;
+ void move(int x, int y) override;
+ std::pair<int, int> getPosition() const override;
+ void show() override;
+ bool visible() const override;
+ void hide() override;
+ void redraw(const Rect& dirty_rect) override;
+ void setCaption(const std::string &caption) override;
+ void grabMouse(bool grab) override;
+ EventQueue getEvents() override;
+ void* getNativeWindowHandle() const override;
+ Point translateToScreen(const Point& point) override;
+
+private:
+ static LRESULT CALLBACK dialogProc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp);
+ static LRESULT CALLBACK subClassProc(HWND hwnd, UINT msg, WPARAM wp,
+ LPARAM lp, UINT_PTR id, DWORD_PTR data);
+
+ HWND parent_window;
+ Window& window;
+ WNDID m_hwnd = 0;
+ bool mouse_in_window{false};
+ std::pair<int, int> last_mouse_position{0, 0};
+ char* m_className = nullptr;
+ EventQueue event_queue;
+ bool always_on_top{false};
+};
+
+} // GUI::
diff --git a/dggui/nativewindow_x11.cc b/dggui/nativewindow_x11.cc
new file mode 100644
index 0000000..33dde7b
--- /dev/null
+++ b/dggui/nativewindow_x11.cc
@@ -0,0 +1,715 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/***************************************************************************
+ * nativewindow_x11.cc
+ *
+ * Fri Dec 28 18:45:57 CET 2012
+ * Copyright 2012 Bent Bisballe Nyeng
+ * deva@aasimon.org
+ ****************************************************************************/
+
+/*
+ * This file is part of DrumGizmo.
+ *
+ * DrumGizmo is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * DrumGizmo is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with DrumGizmo; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+#include "nativewindow_x11.h"
+
+//http://www.mesa3d.org/brianp/xshm.c
+
+#include <X11/Xutil.h>
+#include <sys/ipc.h>
+#include <sys/shm.h>
+#include <cerrno>
+#include <cstring>
+#include <cassert>
+
+#include <chrono>
+
+#include <hugin.hpp>
+
+#include "window.h"
+
+namespace GUI
+{
+
+#define _NET_WM_STATE_REMOVE 0 // remove/unset property
+#define _NET_WM_STATE_ADD 1 // add/set property
+
+void setWindowFront(Display *disp, ::Window wind, bool enable)
+{
+ Atom wm_state, wm_state_above;
+ XEvent event;
+
+ if((wm_state = XInternAtom(disp, "_NET_WM_STATE", False)) == None)
+ {
+ return;
+ }
+
+ if((wm_state_above = XInternAtom(disp, "_NET_WM_STATE_ABOVE", False)) == None)
+ {
+ return;
+ }
+ //
+ //window = the respective client window
+ //message_type = _NET_WM_STATE
+ //format = 32
+ //data.l[0] = the action, as listed below
+ //data.l[1] = first property to alter
+ //data.l[2] = second property to alter
+ //data.l[3] = source indication (0-unk,1-normal app,2-pager)
+ //other data.l[] elements = 0
+ //
+
+ // sending a ClientMessage
+ event.xclient.type = ClientMessage;
+
+ // value unimportant in this case
+ event.xclient.serial = 0;
+
+ // coming from a SendEvent request, so True
+ event.xclient.send_event = True;
+
+ // the event originates from disp
+ event.xclient.display = disp;
+
+ // the window whose state will be modified
+ event.xclient.window = wind;
+
+ // the component Atom being modified in the window
+ event.xclient.message_type = wm_state;
+
+ // specifies that data.l will be used
+ event.xclient.format = 32;
+
+ // 0 is _NET_WM_STATE_REMOVE, 1 is _NET_WM_STATE_ADD
+ event.xclient.data.l[0] =
+ enable ? _NET_WM_STATE_ADD : _NET_WM_STATE_REMOVE;
+
+ // the atom being added
+ event.xclient.data.l[1] = wm_state_above;
+
+ // unused
+ event.xclient.data.l[2] = 0;
+ event.xclient.data.l[3] = 0;
+ event.xclient.data.l[4] = 0;
+
+ // actually send the event
+ XSendEvent(disp, DefaultRootWindow(disp), False,
+ SubstructureRedirectMask | SubstructureNotifyMask, &event);
+}
+
+NativeWindowX11::NativeWindowX11(void* native_window, Window& window)
+ : window(window)
+{
+ display = XOpenDisplay(nullptr);
+ if(display == nullptr)
+ {
+ ERR(X11, "XOpenDisplay failed");
+ return;
+ }
+
+ screen = DefaultScreen(display);
+ visual = DefaultVisual(display, screen);
+ depth = DefaultDepth(display, screen);
+
+ if(native_window)
+ {
+ parent_window = (::Window)native_window;
+
+ // Track size changes on the parent window
+ XSelectInput(display, parent_window, StructureNotifyMask);
+ }
+ else
+ {
+ parent_window = DefaultRootWindow(display);
+ }
+
+ // Create the window
+ XSetWindowAttributes swa;
+ swa.backing_store = Always;
+ xwindow = XCreateWindow(display,
+ parent_window,
+ 0, 0, //window.x(), window.y(),
+ 1, 1, //window.width(), window.height(),
+ 0, // border
+ CopyFromParent, // depth
+ CopyFromParent, // class
+ CopyFromParent, // visual
+ 0,//CWBackingStore,
+ &swa);
+
+ long mask = (StructureNotifyMask |
+ PointerMotionMask |
+ ButtonPressMask |
+ ButtonReleaseMask |
+ KeyPressMask |
+ KeyReleaseMask|
+ ExposureMask |
+ StructureNotifyMask |
+ SubstructureNotifyMask |
+ EnterWindowMask |
+ LeaveWindowMask);
+ XSelectInput(display, xwindow, mask);
+
+ // Register the delete window message:
+ wmDeleteMessage = XInternAtom(display, "WM_DELETE_WINDOW", false);
+
+ Atom protocols[] = { wmDeleteMessage };
+ XSetWMProtocols(display, xwindow, protocols,
+ sizeof(protocols) / sizeof(*protocols));
+
+ // Create a "Graphics Context"
+ gc = XCreateGC(display, xwindow, 0, nullptr);
+}
+
+NativeWindowX11::~NativeWindowX11()
+{
+ if(display == nullptr)
+ {
+ return;
+ }
+
+ deallocateShmImage();
+
+ XFreeGC(display, gc);
+
+ XDestroyWindow(display, xwindow);
+ XCloseDisplay(display);
+}
+
+void NativeWindowX11::setFixedSize(std::size_t width, std::size_t height)
+{
+ if(display == nullptr)
+ {
+ return;
+ }
+
+ resize(width, height);
+
+ XSizeHints size_hints;
+ memset(&size_hints, 0, sizeof(size_hints));
+
+ size_hints.flags = PMinSize|PMaxSize;
+ size_hints.min_width = size_hints.max_width = (int)width;
+ size_hints.min_height = size_hints.max_height = (int)height;
+
+ XSetNormalHints(display, xwindow, &size_hints);
+}
+
+void NativeWindowX11::setAlwaysOnTop(bool always_on_top)
+{
+ setWindowFront(display, xwindow, always_on_top);
+}
+
+void NativeWindowX11::resize(std::size_t width, std::size_t height)
+{
+ if(display == nullptr)
+ {
+ return;
+ }
+
+ XResizeWindow(display, xwindow, width, height);
+}
+
+std::pair<std::size_t, std::size_t> NativeWindowX11::getSize() const
+{
+// XWindowAttributes attributes;
+// XGetWindowAttributes(display, xwindow, &attributes);
+// return std::make_pair(attributes.width, attributes.height);
+
+ ::Window root_window;
+ int x, y;
+ unsigned int width, height, border, depth;
+
+ XGetGeometry(display, xwindow, &root_window,
+ &x, &y,
+ &width, &height, &border, &depth);
+
+ return {width, height};
+}
+
+void NativeWindowX11::move(int x, int y)
+{
+ if(display == nullptr)
+ {
+ return;
+ }
+
+ XMoveWindow(display, xwindow, x, y);
+}
+
+std::pair<int, int> NativeWindowX11::getPosition() const
+{
+ ::Window root_window;
+ ::Window child_window;
+ int x, y;
+ unsigned int width, height, border, depth;
+
+ XGetGeometry(display, xwindow, &root_window,
+ &x, &y,
+ &width, &height, &border, &depth);
+
+ XTranslateCoordinates(display, xwindow, root_window,
+ 0, 0, &x, &y, &child_window);
+
+ return std::make_pair(x, y);
+}
+
+void NativeWindowX11::show()
+{
+ if(display == nullptr)
+ {
+ return;
+ }
+
+ XMapWindow(display, xwindow);
+}
+
+void NativeWindowX11::hide()
+{
+ if(display == nullptr)
+ {
+ return;
+ }
+
+ XUnmapWindow(display, xwindow);
+}
+
+bool NativeWindowX11::visible() const
+{
+ if(display == nullptr)
+ {
+ return false;
+ }
+
+ XWindowAttributes xwa;
+ XGetWindowAttributes(display, xwindow, &xwa);
+ return (xwa.map_state == IsViewable);
+}
+
+void NativeWindowX11::redraw(const Rect& dirty_rect)
+{
+ if(display == nullptr)
+ {
+ return;
+ }
+
+ auto x1 = dirty_rect.x1;
+ auto y1 = dirty_rect.y1;
+ auto x2 = dirty_rect.x2;
+ auto y2 = dirty_rect.y2;
+
+ // Assert that we don't try to paint a backwards rect.
+ assert(x1 <= x2);
+ assert(y1 <= y2);
+
+ updateImageFromBuffer(x1, y1, x2, y2);
+
+ XShmPutImage(display, xwindow, gc, image, x1, y1, x1, y1,
+ std::min((std::size_t)image->width, (x2 - x1)),
+ std::min((std::size_t)image->height, (y2 - y1)), false);
+ XFlush(display);
+}
+
+void NativeWindowX11::setCaption(const std::string &caption)
+{
+ if(display == nullptr)
+ {
+ return;
+ }
+
+ XStoreName(display, xwindow, caption.c_str());
+}
+
+void NativeWindowX11::grabMouse(bool grab)
+{
+ (void)grab;
+ // Don't need to do anything on this platform...
+}
+
+EventQueue NativeWindowX11::getEvents()
+{
+ while(XPending(display))
+ {
+ XEvent xEvent;
+ XNextEvent(display, &xEvent);
+ translateXMessage(xEvent);
+ }
+
+ EventQueue events;
+ std::swap(events, event_queue);
+ return events;
+}
+
+void* NativeWindowX11::getNativeWindowHandle() const
+{
+ return (void*)xwindow;
+}
+
+Point NativeWindowX11::translateToScreen(const Point& point)
+{
+ ::Window child_window;
+ Point p;
+ XTranslateCoordinates(display, xwindow, DefaultRootWindow(display),
+ point.x, point.y, &p.x, &p.y, &child_window);
+ return p;
+}
+
+void NativeWindowX11::translateXMessage(XEvent& xevent)
+{
+ switch(xevent.type)
+ {
+ case MotionNotify:
+ //DEBUG(x11, "MotionNotify");
+ {
+ auto mouseMoveEvent = std::make_shared<MouseMoveEvent>();
+ mouseMoveEvent->x = xevent.xmotion.x;
+ mouseMoveEvent->y = xevent.xmotion.y;
+ event_queue.push_back(mouseMoveEvent);
+ }
+ break;
+
+ case Expose:
+ //DEBUG(x11, "Expose");
+ if(xevent.xexpose.count == 0)
+ {
+ auto repaintEvent = std::make_shared<RepaintEvent>();
+ repaintEvent->x = xevent.xexpose.x;
+ repaintEvent->y = xevent.xexpose.y;
+ repaintEvent->width = xevent.xexpose.width;
+ repaintEvent->height = xevent.xexpose.height;
+ event_queue.push_back(repaintEvent);
+
+ if(image)
+ {
+ // Redraw the entire window.
+ Rect rect{0, 0, window.wpixbuf.width, window.wpixbuf.height};
+ redraw(rect);
+ }
+ }
+ break;
+
+ case ConfigureNotify:
+ //DEBUG(x11, "ConfigureNotify");
+
+ // The parent window size changed, reflect the new size in our own window.
+ if(xevent.xconfigure.window == parent_window)
+ {
+ resize(xevent.xconfigure.width, xevent.xconfigure.height);
+ return;
+ }
+
+ {
+ if((window.width() != (std::size_t)xevent.xconfigure.width) ||
+ (window.height() != (std::size_t)xevent.xconfigure.height))
+ {
+ auto resizeEvent = std::make_shared<ResizeEvent>();
+ resizeEvent->width = xevent.xconfigure.width;
+ resizeEvent->height = xevent.xconfigure.height;
+ event_queue.push_back(resizeEvent);
+ }
+
+ if((window.x() != xevent.xconfigure.x) ||
+ (window.y() != xevent.xconfigure.y))
+ {
+ auto moveEvent = std::make_shared<MoveEvent>();
+ moveEvent->x = xevent.xconfigure.x;
+ moveEvent->y = xevent.xconfigure.y;
+ event_queue.push_back(moveEvent);
+ }
+ }
+ break;
+
+ case ButtonPress:
+ case ButtonRelease:
+ //DEBUG(x11, "ButtonPress");
+ {
+ if((xevent.xbutton.button == 4) || (xevent.xbutton.button == 5))
+ {
+ if(xevent.type == ButtonPress)
+ {
+ int scroll = 1;
+ auto scrollEvent = std::make_shared<ScrollEvent>();
+ scrollEvent->x = xevent.xbutton.x;
+ scrollEvent->y = xevent.xbutton.y;
+ scrollEvent->delta = scroll * ((xevent.xbutton.button == 4) ? -1 : 1);
+ event_queue.push_back(scrollEvent);
+ }
+ }
+ else if ((xevent.xbutton.button == 6) || (xevent.xbutton.button == 7))
+ {
+ // Horizontal scrolling case
+ // FIXME Introduce horizontal scrolling event to handle this.
+ }
+ else
+ {
+ auto buttonEvent = std::make_shared<ButtonEvent>();
+ buttonEvent->x = xevent.xbutton.x;
+ buttonEvent->y = xevent.xbutton.y;
+ switch(xevent.xbutton.button) {
+ case 1:
+ buttonEvent->button = MouseButton::left;
+ break;
+ case 2:
+ buttonEvent->button = MouseButton::middle;
+ break;
+ case 3:
+ buttonEvent->button = MouseButton::right;
+ break;
+ default:
+ WARN(X11, "Unknown button %d, setting to MouseButton::left\n",
+ xevent.xbutton.button);
+ buttonEvent->button = MouseButton::left;
+ break;
+ }
+
+ buttonEvent->direction =
+ (xevent.type == ButtonPress) ?
+ Direction::down : Direction::up;
+
+ // This is a fix for hosts (e.g. those using JUCE) that set the
+ // event time to '0'.
+ if(xevent.xbutton.time == 0)
+ {
+ auto now = std::chrono::system_clock::now().time_since_epoch();
+ xevent.xbutton.time =
+ std::chrono::duration_cast<std::chrono::milliseconds>(now).count();
+ }
+
+ buttonEvent->doubleClick =
+ (xevent.type == ButtonPress) &&
+ ((xevent.xbutton.time - last_click) < 200);
+
+ if(xevent.type == ButtonPress)
+ {
+ last_click = xevent.xbutton.time;
+ }
+ event_queue.push_back(buttonEvent);
+ }
+ }
+ break;
+
+ case KeyPress:
+ case KeyRelease:
+ //DEBUG(x11, "KeyPress");
+ {
+ auto keyEvent = std::make_shared<KeyEvent>();
+
+ switch(xevent.xkey.keycode) {
+ case 113: keyEvent->keycode = Key::left; break;
+ case 114: keyEvent->keycode = Key::right; break;
+ case 111: keyEvent->keycode = Key::up; break;
+ case 116: keyEvent->keycode = Key::down; break;
+ case 119: keyEvent->keycode = Key::deleteKey; break;
+ case 22: keyEvent->keycode = Key::backspace; break;
+ case 110: keyEvent->keycode = Key::home; break;
+ case 115: keyEvent->keycode = Key::end; break;
+ case 117: keyEvent->keycode = Key::pageDown; break;
+ case 112: keyEvent->keycode = Key::pageUp; break;
+ case 36: keyEvent->keycode = Key::enter; break;
+ default: keyEvent->keycode = Key::unknown; break;
+ }
+
+ char stringBuffer[1024];
+ int size = XLookupString(&xevent.xkey, stringBuffer,
+ sizeof(stringBuffer), nullptr, nullptr);
+ if(size && keyEvent->keycode == Key::unknown)
+ {
+ keyEvent->keycode = Key::character;
+ }
+
+ keyEvent->text.append(stringBuffer, size);
+
+ keyEvent->direction =
+ (xevent.type == KeyPress) ? Direction::down : Direction::up;
+
+ event_queue.push_back(keyEvent);
+ }
+ break;
+
+ case ClientMessage:
+ //DEBUG(x11, "ClientMessage");
+ if(((unsigned int)xevent.xclient.data.l[0] == wmDeleteMessage))
+ {
+ auto closeEvent = std::make_shared<CloseEvent>();
+ event_queue.push_back(closeEvent);
+ }
+ break;
+
+ case EnterNotify:
+ //DEBUG(x11, "EnterNotify");
+ {
+ auto enterEvent = std::make_shared<MouseEnterEvent>();
+ enterEvent->x = xevent.xcrossing.x;
+ enterEvent->y = xevent.xcrossing.y;
+ event_queue.push_back(enterEvent);
+ }
+ break;
+
+ case LeaveNotify:
+ //DEBUG(x11, "LeaveNotify");
+ {
+ auto leaveEvent = std::make_shared<MouseLeaveEvent>();
+ leaveEvent->x = xevent.xcrossing.x;
+ leaveEvent->y = xevent.xcrossing.y;
+ event_queue.push_back(leaveEvent);
+ }
+ break;
+
+ case MapNotify:
+ case MappingNotify:
+ //DEBUG(x11, "EnterNotify");
+ // There's nothing to do here atm.
+ break;
+
+ default:
+ WARN(X11, "Unhandled xevent.type: %d\n", xevent.type);
+ break;
+ }
+}
+
+void NativeWindowX11::allocateShmImage(std::size_t width, std::size_t height)
+{
+ DEBUG(x11, "(Re)alloc XShmImage (%d, %d)", (int)width, (int)height);
+
+ if(image)
+ {
+ deallocateShmImage();
+ }
+
+ if(!XShmQueryExtension(display))
+ {
+ ERR(x11, "XShmExtension not available");
+ return;
+ }
+
+ image = XShmCreateImage(display, visual, depth,
+ ZPixmap, nullptr, &shm_info,
+ width, height);
+ if(image == nullptr)
+ {
+ ERR(x11, "XShmCreateImage failed!\n");
+ return;
+ }
+
+ std::size_t byte_size = image->bytes_per_line * image->height;
+
+ // Allocate shm buffer
+ int shm_id = shmget(IPC_PRIVATE, byte_size, IPC_CREAT|0777);
+ if(shm_id == -1)
+ {
+ ERR(x11, "shmget failed: %s", strerror(errno));
+ return;
+ }
+
+ shm_info.shmid = shm_id;
+
+ // Attach share memory bufer
+ void* shm_addr = shmat(shm_id, nullptr, 0);
+ if(reinterpret_cast<long int>(shm_addr) == -1)
+ {
+ ERR(x11, "shmat failed: %s", strerror(errno));
+ return;
+ }
+
+ shm_info.shmaddr = reinterpret_cast<char*>(shm_addr);
+ image->data = shm_info.shmaddr;
+ shm_info.readOnly = false;
+
+ // This may trigger the X protocol error we're ready to catch:
+ XShmAttach(display, &shm_info);
+ XSync(display, false);
+
+ // Make the shm id unavailable to others
+ shmctl(shm_id, IPC_RMID, 0);
+}
+
+void NativeWindowX11::deallocateShmImage()
+{
+ if(image == nullptr)
+ {
+ return;
+ }
+
+ XFlush(display);
+ XShmDetach(display, &shm_info);
+ XDestroyImage(image);
+ image = nullptr;
+ shmdt(shm_info.shmaddr);
+}
+
+void NativeWindowX11::updateImageFromBuffer(std::size_t x1, std::size_t y1,
+ std::size_t x2, std::size_t y2)
+{
+ //DEBUG(x11, "depth: %d", depth);
+
+ auto width = window.wpixbuf.width;
+ auto height = window.wpixbuf.height;
+
+ // If image hasn't been allocated yet or if the image backbuffer is
+ // too small, (re)allocate with a suitable size.
+ if((image == nullptr) ||
+ ((int)width > image->width) ||
+ ((int)height > image->height))
+ {
+ constexpr std::size_t step_size = 128; // size increments
+ std::size_t new_width = ((width / step_size) + 1) * step_size;
+ std::size_t new_height = ((height / step_size) + 1) * step_size;
+ allocateShmImage(new_width, new_height);
+ x1 = 0;
+ y1 = 0;
+ x2 = width;
+ y2 = height;
+ }
+
+ auto stride = image->width;
+
+ std::uint8_t* pixel_buffer = (std::uint8_t*)window.wpixbuf.buf;
+ if(depth >= 24) // RGB 888 format
+ {
+ std::uint32_t* shm_addr = (std::uint32_t*)shm_info.shmaddr;
+ for(std::size_t y = y1; y < y2; ++y)
+ {
+ for(std::size_t x = x1; x < x2; ++x)
+ {
+ const std::size_t pin = y * width + x;
+ const std::size_t pout = y * stride + x;
+ const std::uint8_t red = pixel_buffer[pin * 3];
+ const std::uint8_t green = pixel_buffer[pin * 3 + 1];
+ const std::uint8_t blue = pixel_buffer[pin * 3 + 2];
+ shm_addr[pout] = (red << 16) | (green << 8) | blue;
+ }
+ }
+ }
+ else if(depth >= 15) // RGB 565 format
+ {
+ std::uint16_t* shm_addr = (std::uint16_t*)shm_info.shmaddr;
+
+ for(std::size_t y = y1; y < y2; ++y)
+ {
+ for(std::size_t x = x1; x < x2; ++x)
+ {
+ const std::size_t pin = y * width + x;
+ const std::size_t pout = y * stride + x;
+ const std::uint8_t red = pixel_buffer[pin * 3];
+ const std::uint8_t green = pixel_buffer[pin * 3 + 1];
+ const std::uint8_t blue = pixel_buffer[pin * 3 + 2];
+ shm_addr[pout] = ((red >> 3) << 11) | ((green >> 2) << 5) | (blue >> 3);
+ }
+ }
+ }
+}
+
+} // GUI::
diff --git a/dggui/nativewindow_x11.h b/dggui/nativewindow_x11.h
new file mode 100644
index 0000000..cb56fbc
--- /dev/null
+++ b/dggui/nativewindow_x11.h
@@ -0,0 +1,99 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/***************************************************************************
+ * nativewindow_x11.h
+ *
+ * Fri Dec 28 18:45:56 CET 2012
+ * Copyright 2012 Bent Bisballe Nyeng
+ * deva@aasimon.org
+ ****************************************************************************/
+
+/*
+ * This file is part of DrumGizmo.
+ *
+ * DrumGizmo is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * DrumGizmo is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with DrumGizmo; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+#pragma once
+
+#include <queue>
+
+#include <X11/Xlib.h>
+#include <X11/extensions/XShm.h>
+
+#include "nativewindow.h"
+
+namespace GUI
+{
+
+class Window;
+
+class NativeWindowX11
+ : public NativeWindow
+{
+public:
+ NativeWindowX11(void* native_window, Window& window);
+ ~NativeWindowX11();
+
+ // From NativeWindow:
+ void setFixedSize(std::size_t width, std::size_t height) override;
+ void setAlwaysOnTop(bool always_on_top) override;
+ void resize(std::size_t width, std::size_t height) override;
+ std::pair<std::size_t, std::size_t> getSize() const override;
+ void move(int x, int y) override;
+ std::pair<int, int> getPosition() const override;
+ void show() override;
+ void hide() override;
+ bool visible() const override;
+ void setCaption(const std::string &caption) override;
+ void redraw(const Rect& dirty_rect) override;
+ void grabMouse(bool grab) override;
+ EventQueue getEvents() override;
+ void* getNativeWindowHandle() const override;
+ Point translateToScreen(const Point& point) override;
+
+private:
+ void translateXMessage(XEvent& xevent);
+
+ //! Allocate new shared memory buffer for the pixel buffer.
+ //! Frees the existing buffer if there is one.
+ void allocateShmImage(std::size_t width, std::size_t height);
+
+ //! Deallocate image and shm resources.
+ void deallocateShmImage();
+
+ //! Copy data from the pixel buffer into the shared memory
+ void updateImageFromBuffer(std::size_t x1, std::size_t y1,
+ std::size_t x2, std::size_t y2);
+
+ XShmSegmentInfo shm_info;
+ XImage* image{nullptr};
+
+ ::Window xwindow{0};
+ GC gc{0};
+
+ Window& window;
+
+ Time last_click{0};
+
+ Display* display{nullptr};
+ int screen{0};
+ int depth{0};
+ Visual* visual{nullptr};
+ Atom wmDeleteMessage{0};
+ ::Window parent_window;
+
+ EventQueue event_queue;
+};
+
+} // GUI::
diff --git a/dggui/painter.cc b/dggui/painter.cc
new file mode 100644
index 0000000..f746f83
--- /dev/null
+++ b/dggui/painter.cc
@@ -0,0 +1,644 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/***************************************************************************
+ * painter.cc
+ *
+ * Wed Oct 12 19:48:45 CEST 2011
+ * Copyright 2011 Bent Bisballe Nyeng
+ * deva@aasimon.org
+ ****************************************************************************/
+
+/*
+ * This file is part of DrumGizmo.
+ *
+ * DrumGizmo is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * DrumGizmo is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with DrumGizmo; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+#include "painter.h"
+
+#include <cmath>
+#include <cassert>
+
+#include "pixelbuffer.h"
+#include "font.h"
+#include "drawable.h"
+#include "image.h"
+#include "canvas.h"
+
+namespace GUI
+{
+
+Painter::Painter(Canvas& canvas)
+ : pixbuf(canvas.getPixelBuffer())
+{
+ colour = Colour(0.0f, 0.0f, 0.0f, 0.5f);
+}
+
+Painter::~Painter()
+{
+}
+
+void Painter::setColour(const Colour& colour)
+{
+ this->colour = colour;
+}
+
+static void plot(PixelBufferAlpha& pixbuf, const Colour& colour,
+ int x, int y, double c)
+{
+ if((x >= (int)pixbuf.width) ||
+ (y >= (int)pixbuf.height) ||
+ (x < 0) ||
+ (y < 0))
+ {
+ return;
+ }
+
+ // plot the pixel at (x, y) with brightness c (where 0 ≤ c ≤ 1)
+ Colour col(colour);
+ if(c != 1)
+ {
+ col.data()[3] *= c;
+ }
+ pixbuf.addPixel(x, y, col);
+}
+
+static inline double fpart(double x)
+{
+ return x - std::floor(x);// fractional part of x
+}
+
+static inline double rfpart(double x)
+{
+ return 1 - fpart(x); // reverse fractional part of x
+}
+
+void Painter::drawLine(int x0, int y0, int x1, int y1)
+{
+ bool steep = abs(y1 - y0) > abs(x1 - x0);
+
+ if(steep)
+ {
+ std::swap(x0, y0);
+ std::swap(x1, y1);
+ }
+ if(x0 > x1)
+ {
+ std::swap(x0, x1);
+ std::swap(y0, y1);
+ }
+
+ double dx = x1 - x0;
+ double dy = y1 - y0;
+ double gradient = dy / dx;
+
+ // Handle first endpoint:
+ double xend = std::round(x0);
+ double yend = y0 + gradient * (xend - x0);
+
+ double xpxl1 = xend; // this will be used in the main loop
+ double ypxl1 = std::floor(yend);
+
+ if(steep)
+ {
+ pixbuf.addPixel(ypxl1, xpxl1, colour);
+ }
+ else
+ {
+ pixbuf.addPixel(xpxl1, ypxl1, colour);
+ }
+
+ double intery = yend + gradient; // first y-intersection for the main loop
+
+ // Handle second endpoint:
+ xend = std::round(x1);
+ yend = y1 + gradient * (xend - x1);
+
+ double xpxl2 = xend; // this will be used in the main loop
+ double ypxl2 = std::floor(yend);
+
+ if(steep)
+ {
+ pixbuf.addPixel(ypxl2, xpxl2, colour);
+ }
+ else
+ {
+ pixbuf.addPixel(xpxl2, ypxl2, colour);
+ }
+
+ // main loop
+ for(int x = xpxl1 + 1; x <= xpxl2 - 1; ++x)
+ {
+ if(steep)
+ {
+ plot(pixbuf, colour, std::floor(intery) , x, rfpart(intery));
+ plot(pixbuf, colour, std::floor(intery)+1, x, fpart(intery));
+ }
+ else
+ {
+ plot(pixbuf, colour, x, std::floor(intery), rfpart(intery));
+ plot(pixbuf, colour, x, std::floor(intery)+1, fpart(intery));
+ }
+ intery += gradient;
+ }
+}
+
+void Painter::drawRectangle(int x1, int y1, int x2, int y2)
+{
+ drawLine(x1, y1, x2 - 1, y1);
+ drawLine(x2, y1, x2, y2 - 1);
+ drawLine(x1 + 1, y2, x2, y2);
+ drawLine(x1, y1 + 1, x1, y2);
+}
+
+void Painter::drawFilledRectangle(int x1, int y1, int x2, int y2)
+{
+ for(int y = y1; y <= y2; ++y)
+ {
+ drawLine(x1, y, x2, y);
+ }
+}
+
+void Painter::clear()
+{
+ pixbuf.clear();
+}
+
+void Painter::drawText(int x0, int y0, const Font& font,
+ const std::string& text, bool nocolour, bool rotate)
+{
+ PixelBufferAlpha* textbuf = font.render(text);
+
+ if(!rotate)
+ {
+ y0 -= textbuf->height; // The y0 offset (baseline) is the bottom of the text.
+ }
+
+ // If the text offset is outside the buffer; skip it.
+ if((x0 > (int)pixbuf.width) || (y0 > (int)pixbuf.height))
+ {
+ delete textbuf;
+ return;
+ }
+
+ // Make sure we don't try to draw outside the pixbuf.
+ int renderWidth = textbuf->width;
+ if(renderWidth > (int)(pixbuf.width - x0))
+ {
+ renderWidth = pixbuf.width - x0;
+ }
+
+ int renderHeight = textbuf->height;
+ if(renderHeight > ((int)pixbuf.height - y0))
+ {
+ renderHeight = ((int)pixbuf.height - y0);
+ }
+
+ if(nocolour)
+ {
+ for(int y = -1 * std::min(0, y0); y < renderHeight; ++y)
+ {
+ int x = -1 * std::min(0, x0);
+
+ assert(x >= 0);
+ assert(y >= 0);
+ assert(x < (int)textbuf->width);
+ assert(y < (int)textbuf->height);
+
+ auto c = textbuf->getLine(x, y);
+
+ assert(x + x0 >= 0);
+ assert(y + y0 >= 0);
+ assert(x + x0 < (int)pixbuf.width);
+ assert(y + y0 < (int)pixbuf.height);
+
+ pixbuf.blendLine(x + x0, y + y0, c, renderWidth - x);
+ }
+ }
+ else if(rotate)
+ {
+ int renderWidth = textbuf->height;
+ if(renderWidth > (int)(pixbuf.width - x0))
+ {
+ renderWidth = pixbuf.width - x0;
+ }
+
+ int renderHeight = textbuf->width;
+ if(renderHeight > ((int)pixbuf.height - y0))
+ {
+ renderHeight = ((int)pixbuf.height - y0);
+ }
+
+ for(int y = -1 * std::min(0, y0); y < renderHeight; ++y)
+ {
+ for(int x = -1 * std::min(0, x0); x < renderWidth; ++x)
+ {
+ assert(x >= 0);
+ assert(y >= 0);
+ assert(x < (int)textbuf->height);
+ assert(y < (int)textbuf->width);
+
+ auto c = textbuf->pixel(textbuf->width - y - 1, x);
+
+ assert(x + x0 >= 0);
+ assert(y + y0 >= 0);
+ assert(x + x0 < (int)pixbuf.width);
+ assert(y + y0 < (int)pixbuf.height);
+
+ Colour col(colour.red(), colour.green(),
+ colour.blue(), (int)(colour.alpha() * c.alpha()) / 255);
+ pixbuf.addPixel(x + x0, y + y0, col);
+ }
+ }
+ }
+ else
+ {
+ for(int y = -1 * std::min(0, y0); y < renderHeight; ++y)
+ {
+ for(int x = -1 * std::min(0, x0); x < renderWidth; ++x)
+ {
+ assert(x >= 0);
+ assert(y >= 0);
+ assert(x < (int)textbuf->width);
+ assert(y < (int)textbuf->height);
+
+ auto c = textbuf->pixel(x, y);
+
+ assert(x + x0 >= 0);
+ assert(y + y0 >= 0);
+ assert(x + x0 < (int)pixbuf.width);
+ assert(y + y0 < (int)pixbuf.height);
+
+ Colour col(colour.red(), colour.green(),
+ colour.blue(), (int)(colour.alpha() * c.alpha()) / 255);
+ pixbuf.addPixel(x + x0, y + y0, col);
+ }
+ }
+ }
+
+ delete textbuf;
+}
+
+void Painter::drawPoint(int x, int y)
+{
+ if(x >= 0 && y >= 0 && (std::size_t)x < pixbuf.width && (std::size_t)y < pixbuf.height)
+ {
+ pixbuf.setPixel(x, y, colour);
+ }
+}
+
+static void plot4points(Painter *p, int cx, int cy, int x, int y)
+{
+ p->drawPoint(cx + x, cy + y);
+ if(x != 0)
+ {
+ p->drawPoint(cx - x, cy + y);
+ }
+
+ if(y != 0)
+ {
+ p->drawPoint(cx + x, cy - y);
+ }
+
+ if(x != 0 && y != 0)
+ {
+ p->drawPoint(cx - x, cy - y);
+ }
+}
+
+void Painter::drawCircle(int cx, int cy, double radius)
+{
+ int error = -radius;
+ int x = radius;
+ int y = 0;
+
+ while(x >= y)
+ {
+ plot4points(this, cx, cy, x, y);
+
+ if(x != y)
+ {
+ plot4points(this, cx, cy, y, x);
+ }
+
+ error += y;
+ ++y;
+ error += y;
+
+ if(error >= 0)
+ {
+ --x;
+ error -= x;
+ error -= x;
+ }
+ }
+}
+
+static void plot4lines(Painter *p, int cx, int cy, int x, int y)
+{
+ p->drawLine(cx + x, cy + y, cx - x, cy + y);
+ if(x != 0)
+ {
+ p->drawLine(cx - x, cy + y, cx + x, cy + y);
+ }
+
+ if(y != 0)
+ {
+ p->drawLine(cx + x, cy - y, cx - x, cy - y);
+ }
+
+ if(x != 0 && y != 0)
+ {
+ p->drawLine(cx - x, cy - y, cx + x, cy - y);
+ }
+}
+
+void Painter::drawFilledCircle(int cx, int cy, int radius)
+{
+ int error = -radius;
+ int x = radius;
+ int y = 0;
+
+ while(x >= y)
+ {
+ plot4lines(this, cx, cy, x, y);
+
+ if(x != y)
+ {
+ plot4lines(this, cx, cy, y, x);
+ }
+
+ error += y;
+ ++y;
+ error += y;
+
+ if(error >= 0)
+ {
+ --x;
+ error -= x;
+ error -= x;
+ }
+ }
+}
+
+void Painter::drawImage(int x0, int y0, const Drawable& image)
+{
+ int fw = image.width();
+ int fh = image.height();
+
+ // Make sure we don't try to draw outside the pixbuf.
+ if(fw > (int)(pixbuf.width - x0))
+ {
+ fw = (int)(pixbuf.width - x0);
+ }
+
+ if(fh > (int)(pixbuf.height - y0))
+ {
+ fh = (int)(pixbuf.height - y0);
+ }
+
+ if((fw < 1) || (fh < 1))
+ {
+ return;
+ }
+
+ if(image.hasAlpha())
+ {
+ if(!image.line(0))
+ {
+ for(std::size_t y = -1 * std::min(0, y0); y < (std::size_t)fh; ++y)
+ {
+ for(std::size_t x = -1 * std::min(0, x0); x < (std::size_t)fw; ++x)
+ {
+ assert(x >= 0);
+ assert(y >= 0);
+ assert(x < image.width());
+ assert(y < image.height());
+ auto& c = image.getPixel(x, y);
+
+ assert(x0 + x >= 0);
+ assert(y0 + y >= 0);
+ assert(x0 + x < pixbuf.width);
+ assert(y0 + y < pixbuf.height);
+
+ pixbuf.addPixel(x0 + x, y0 + y, c);
+ }
+ }
+ }
+ else
+ {
+ std::size_t x_offset = -1 * std::min(0, x0);
+ for(std::size_t y = -1 * std::min(0, y0); y < (std::size_t)fh; ++y)
+ {
+ pixbuf.blendLine(x_offset + x0, y + y0, image.line(y, x_offset),
+ std::min((int)image.width(), fw - (int)x_offset));
+ }
+ }
+ }
+ else
+ {
+ std::size_t x_offset = -1 * std::min(0, x0);
+ for(std::size_t y = -1 * std::min(0, y0); y < (std::size_t)fh; ++y)
+ {
+ pixbuf.writeLine(x_offset + x0, y + y0, image.line(y, x_offset),
+ std::min((int)image.width(), fw - (int)x_offset));
+ }
+ }
+}
+
+void Painter::drawRestrictedImage(int x0, int y0,
+ const Colour& restriction_colour,
+ const Drawable& image)
+{
+ int fw = image.width();
+ int fh = image.height();
+
+ // Make sure we don't try to draw outside the pixbuf.
+ if(fw > (int)(pixbuf.width - x0))
+ {
+ fw = (int)(pixbuf.width - x0);
+ }
+
+ if(fh > (int)(pixbuf.height - y0))
+ {
+ fh = (int)(pixbuf.height - y0);
+ }
+
+ if((fw < 1) || (fh < 1))
+ {
+ return;
+ }
+
+ for(std::size_t y = -1 * std::min(0, y0); y < (std::size_t)fh; ++y)
+ {
+ for(std::size_t x = -1 * std::min(0, x0); x < (std::size_t)fw; ++x)
+ {
+ assert(x >= 0);
+ assert(y >= 0);
+ assert(x < image.width());
+ assert(y < image.height());
+ auto& c = image.getPixel(x, y);
+
+ assert(x0 + x >= 0);
+ assert(y0 + y >= 0);
+ assert(x0 + x < pixbuf.width);
+ assert(y0 + y < pixbuf.height);
+
+ if(c == restriction_colour)
+ {
+ pixbuf.setPixel(x0 + x, y0 + y, c);
+ }
+ }
+ }
+}
+
+void Painter::drawImageStretched(int x0, int y0, const Drawable& image,
+ int width, int height)
+{
+ float fw = image.width();
+ float fh = image.height();
+
+ // Make sure we don't try to draw outside the pixbuf.
+ if(width > (int)(pixbuf.width - x0))
+ {
+ width = pixbuf.width - x0;
+ }
+
+ if(height > (int)(pixbuf.height - y0))
+ {
+ height = pixbuf.height - y0;
+ }
+
+ if((width < 1) || (height < 1))
+ {
+ return;
+ }
+
+ for(int y = -1 * std::min(0, y0); y < height; ++y)
+ {
+ for(int x = -1 * std::min(0, x0); x < width; ++x)
+ {
+ int lx = ((float)x / (float)width) * fw;
+ int ly = ((float)y / (float)height) * fh;
+ auto& c = image.getPixel(lx, ly);
+ pixbuf.addPixel(x0 + x, y0 + y, c);
+ }
+ }
+}
+
+void Painter::drawBox(int x, int y, const Box& box, int width, int height)
+{
+ int dx = x;
+ int dy = y;
+
+ // Top:
+ drawImage(dx, dy, *box.topLeft);
+
+ dx += box.topLeft->width();
+ if((dx < 0) || (dy < 0))
+ {
+ return;
+ }
+
+ drawImageStretched(dx, dy, *box.top,
+ width - box.topRight->width() - box.topLeft->width(),
+ box.top->height());
+
+ dx = x + width - box.topRight->width();
+ if((dx < 0) || (dy < 0))
+ {
+ return;
+ }
+
+ drawImage(dx, dy, *box.topRight);
+
+ // Center:
+ dy = y + box.topLeft->height();
+ dx = x + box.left->width();
+ if((dx < 0) || (dy < 0))
+ {
+ return;
+ }
+
+ drawImageStretched(dx, dy, *box.center,
+ width - box.left->width() - box.right->width(),
+ height - box.topLeft->height() - box.bottomLeft->height());
+
+ // Mid:
+ dx = x;
+ dy = y + box.topLeft->height();
+ if((dx < 0) || (dy < 0))
+ {
+ return;
+ }
+
+ drawImageStretched(dx, dy, *box.left, box.left->width(),
+ height - box.topLeft->height() - box.bottomLeft->height());
+
+ dx = x + width - box.right->width();
+ dy = y + box.topRight->height();
+ if((dx < 0) || (dy < 0))
+ {
+ return;
+ }
+
+ drawImageStretched(dx, dy, *box.right,
+ box.right->width(),
+ height - box.topRight->height() - box.bottomRight->height());
+
+ // Bottom:
+ dx = x;
+ dy = y + height - box.bottomLeft->height();
+ if((dx < 0) || (dy < 0))
+ {
+ return;
+ }
+
+ drawImage(dx, dy, *box.bottomLeft);
+
+ dx += box.bottomLeft->width();
+ if((dx < 0) || (dy < 0))
+ {
+ return;
+ }
+
+ drawImageStretched(dx, dy, *box.bottom,
+ width - box.bottomRight->width() - box.bottomLeft->width(),
+ box.bottom->height());
+
+ dx = x + width - box.bottomRight->width();
+ if((dx < 0) || (dy < 0))
+ {
+ return;
+ }
+
+ drawImage(dx, dy, *box.bottomRight);
+}
+
+void Painter::drawBar(int x, int y, const Bar& bar, int width, int height)
+{
+ if(width < ((int)bar.left->width() + (int)bar.right->width() + 1))
+ {
+ width = bar.left->width() + bar.right->width() + 1;
+ }
+
+ drawImageStretched(x, y, *bar.left, bar.left->width(), height);
+
+ drawImageStretched(x + bar.left->width(), y, *bar.center,
+ width - bar.left->width() - bar.right->width(), height);
+
+ drawImageStretched(x + width - bar.left->width(), y, *bar.right,
+ bar.right->width(), height);
+}
+
+} // GUI::
diff --git a/dggui/painter.h b/dggui/painter.h
new file mode 100644
index 0000000..9bf7fbf
--- /dev/null
+++ b/dggui/painter.h
@@ -0,0 +1,103 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/***************************************************************************
+ * painter.h
+ *
+ * Wed Oct 12 19:48:45 CEST 2011
+ * Copyright 2011 Bent Bisballe Nyeng
+ * deva@aasimon.org
+ ****************************************************************************/
+
+/*
+ * This file is part of DrumGizmo.
+ *
+ * DrumGizmo is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * DrumGizmo is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with DrumGizmo; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+#pragma once
+
+#include <string>
+
+#include "colour.h"
+#include "pixelbuffer.h"
+
+namespace GUI
+{
+
+class Font;
+class Drawable;
+class Image;
+class Canvas;
+
+class Painter
+{
+public:
+ Painter(Canvas& canvas);
+ ~Painter();
+
+ void setColour(const Colour& colour);
+
+ void drawLine(int x1, int y1, int x2, int y2);
+ void drawText(int x, int y, const Font& font, const std::string& text,
+ bool nocolour = false, bool rotate = false);
+ void drawRectangle(int x1, int y1, int x2, int y2);
+ void drawFilledRectangle(int x1, int y1, int x2, int y2);
+ void drawPoint(int x, int y);
+ void drawCircle(int x, int y, double r);
+ void drawFilledCircle(int x, int y, int r);
+ void drawImage(int x, int y, const Drawable& image);
+ void drawRestrictedImage(int x0, int y0, const Colour& restriction_colour,
+ const Drawable& image);
+ void drawImageStretched(int x, int y, const Drawable& image,
+ int width, int height);
+
+ template<typename Iterator>
+ void draw(Iterator begin, Iterator end, int x_offset, int y_offset, Colour const& colour);
+
+ typedef struct {
+ Image* topLeft;
+ Image* top;
+ Image* topRight;
+ Image* left;
+ Image* right;
+ Image* bottomLeft;
+ Image* bottom;
+ Image* bottomRight;
+ Image* center;
+ } Box;
+ void drawBox(int x, int y, const Box& box, int width, int height);
+
+ typedef struct {
+ Image* left;
+ Image* right;
+ Image* center;
+ } Bar;
+ void drawBar(int x, int y, const Bar& bar, int width, int height);
+
+ void clear();
+
+private:
+ PixelBufferAlpha& pixbuf;
+ Colour colour;
+};
+
+template<typename Iterator>
+void Painter::draw(Iterator begin, Iterator end, int x_offset, int y_offset, Colour const& colour)
+{
+ for (auto it = begin; it != end; ++it)
+ {
+ pixbuf.addPixel(x_offset + it->x, y_offset + it->y, colour);
+ }
+}
+
+} // GUI::
diff --git a/dggui/pixelbuffer.cc b/dggui/pixelbuffer.cc
new file mode 100644
index 0000000..3c666cd
--- /dev/null
+++ b/dggui/pixelbuffer.cc
@@ -0,0 +1,369 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/***************************************************************************
+ * pixelbuffer.cc
+ *
+ * Thu Nov 10 09:00:38 CET 2011
+ * Copyright 2011 Bent Bisballe Nyeng
+ * deva@aasimon.org
+ ****************************************************************************/
+
+/*
+ * This file is part of DrumGizmo.
+ *
+ * DrumGizmo is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * DrumGizmo is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with DrumGizmo; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+#include "pixelbuffer.h"
+
+#include <cassert>
+
+#include <cstdlib>
+#include <cstring>
+#include <algorithm>
+
+namespace GUI
+{
+
+PixelBuffer::PixelBuffer(std::size_t width, std::size_t height)
+{
+ realloc(width, height);
+}
+
+PixelBuffer::~PixelBuffer()
+{
+}
+
+void PixelBuffer::realloc(std::size_t width, std::size_t height)
+{
+ buf_data.resize(width * height * 3);
+ buf = buf_data.data();
+ this->width = width;
+ this->height = height;
+}
+
+void PixelBuffer::blendLine(std::size_t x, std::size_t y,
+ const std::uint8_t* line, std::size_t len)
+{
+ std::uint8_t* target = buf + (x + y * width) * 3;
+ while(len)
+ {
+ if(line[3] == 0xff)
+ {
+ std::memcpy(target, line, 3);
+ }
+ else
+ {
+ unsigned int a = line[3];
+ unsigned int b = 255 - a;
+
+ target[0] = (std::uint8_t)((line[0] * a + target[0] * b) / 255);
+ target[1] = (std::uint8_t)((line[1] * a + target[1] * b) / 255);
+ target[2] = (std::uint8_t)((line[2] * a + target[2] * b) / 255);
+ }
+ target += 3;
+ line += 4;
+ --len;
+ }
+}
+
+Rect PixelBuffer::updateBuffer(std::vector<PixelBufferAlpha*>& pixel_buffers)
+{
+ bool has_dirty_rect{false};
+ Rect dirty_rect;
+
+ for(const auto& pixel_buffer : pixel_buffers)
+ {
+ if(pixel_buffer->dirty)
+ {
+ auto x1 = (std::size_t)std::max(pixel_buffer->x, 0);
+ auto x2 = (std::size_t)std::max((pixel_buffer->x + (int)pixel_buffer->width), 0);
+ auto y1 = (std::size_t)std::max(pixel_buffer->y, 0);
+ auto y2 = (std::size_t)std::max((pixel_buffer->y + (int)pixel_buffer->height), 0);
+
+ pixel_buffer->dirty = false;
+ if(!has_dirty_rect)
+ {
+ // Insert this area:
+ dirty_rect = {x1, y1, x2, y2};
+ has_dirty_rect = true;
+ }
+ else
+ {
+ // Expand existing area:
+ auto x1_0 = dirty_rect.x1;
+ auto y1_0 = dirty_rect.y1;
+ auto x2_0 = dirty_rect.x2;
+ auto y2_0 = dirty_rect.y2;
+ dirty_rect = {
+ (x1_0 < x1) ? x1_0 : x1,
+ (y1_0 < y1) ? y1_0 : y1,
+ (x2_0 > x2) ? x2_0 : x2,
+ (y2_0 > y2) ? y2_0 : y2
+ };
+ }
+ }
+
+ if(pixel_buffer->has_last)
+ {
+ auto x1 = (std::size_t)pixel_buffer->last_x;
+ auto x2 = (std::size_t)(pixel_buffer->last_x + pixel_buffer->last_width);
+ auto y1 = (std::size_t)pixel_buffer->last_y;
+ auto y2 = (std::size_t)(pixel_buffer->last_y + pixel_buffer->last_height);
+
+ pixel_buffer->has_last = false;
+ if(!has_dirty_rect)
+ {
+ // Insert this area:
+ dirty_rect = {x1, y1, x2, y2};
+ has_dirty_rect = true;
+ }
+ else
+ {
+ // Expand existing area:
+ auto x1_0 = dirty_rect.x1;
+ auto y1_0 = dirty_rect.y1;
+ auto x2_0 = dirty_rect.x2;
+ auto y2_0 = dirty_rect.y2;
+ dirty_rect = {
+ (x1_0 < x1) ? x1_0 : x1,
+ (y1_0 < y1) ? y1_0 : y1,
+ (x2_0 > x2) ? x2_0 : x2,
+ (y2_0 > y2) ? y2_0 : y2
+ };
+ }
+ }
+ }
+
+ if(!has_dirty_rect)
+ {
+ return {};
+ }
+
+ for(const auto& pixel_buffer : pixel_buffers)
+ {
+ if(!pixel_buffer->visible)
+ {
+ continue;
+ }
+
+ int update_width = pixel_buffer->width;
+ int update_height = pixel_buffer->height;
+
+ // Skip buffer if not inside window.
+ if(((int)width < pixel_buffer->x) ||
+ ((int)height < pixel_buffer->y))
+ {
+ continue;
+ }
+
+ if(update_width > ((int)width - pixel_buffer->x))
+ {
+ update_width = ((int)width - pixel_buffer->x);
+ }
+
+ if(update_height > ((int)height - pixel_buffer->y))
+ {
+ update_height = ((int)height - pixel_buffer->y);
+ }
+
+ auto from_x = (int)dirty_rect.x1 - pixel_buffer->x;
+ from_x = std::max(0, from_x);
+ auto from_y = (int)dirty_rect.y1 - pixel_buffer->y;
+ from_y = std::max(0, from_y);
+
+ auto to_x = (int)dirty_rect.x2 - pixel_buffer->x;
+ to_x = std::min(to_x, (int)update_width);
+ auto to_y = (int)dirty_rect.y2 - pixel_buffer->y;
+ to_y = std::min(to_y, (int)update_height);
+
+ if(to_x < from_x)
+ {
+ continue;
+ }
+
+ for(int y = from_y; y < to_y; y++)
+ {
+ blendLine(pixel_buffer->x + from_x,
+ pixel_buffer->y + y,
+ pixel_buffer->getLine(from_x, y),
+ to_x - from_x);
+ }
+ }
+
+ dirty_rect.x2 = std::min(width, dirty_rect.x2);
+ dirty_rect.y2 = std::min(height, dirty_rect.y2);
+
+ // Make sure we don't try to paint a rect backwards.
+ if(dirty_rect.x1 > dirty_rect.x2)
+ {
+ std::swap(dirty_rect.x1, dirty_rect.x2);
+ }
+
+ if(dirty_rect.y1 > dirty_rect.y2)
+ {
+ std::swap(dirty_rect.y1, dirty_rect.y2);
+ }
+
+ return dirty_rect;
+}
+
+PixelBufferAlpha::PixelBufferAlpha(std::size_t width, std::size_t height)
+{
+ realloc(width, height);
+}
+
+PixelBufferAlpha::~PixelBufferAlpha()
+{
+}
+
+void PixelBufferAlpha::realloc(std::size_t width, std::size_t height)
+{
+ buf_data.resize(width * height * 4);
+ buf = buf_data.data();
+ this->width = width;
+ this->height = height;
+ clear();
+}
+
+void PixelBufferAlpha::clear()
+{
+ std::memset(buf, 0, width * height * 4);
+}
+
+void PixelBufferAlpha::setPixel(std::size_t x, std::size_t y, const Colour& c)
+{
+ std::uint8_t* pixel = buf + (x + y * width) * 4;
+ std::memcpy(pixel, c.data(), 4);
+}
+
+void PixelBufferAlpha::writeLine(std::size_t x, std::size_t y,
+ const std::uint8_t* line, std::size_t len)
+{
+ if(x >= width || y >= height)
+ {
+ return;
+ }
+
+ if(x + len > width)
+ {
+ len = width - x;
+ }
+
+ auto offset = buf + (x + y * width) * 4;
+
+ std::memcpy(offset, line, len * 4);
+}
+
+
+// SIMD: https://github.com/WojciechMula/toys/blob/master/blend_32bpp/blend_32bpp.c
+// Alpha blending: http://en.wikipedia.org/wiki/Alpha_compositing
+
+void PixelBufferAlpha::blendLine(std::size_t x, std::size_t y,
+ const std::uint8_t* line, std::size_t len)
+{
+ if(x >= width || y >= height)
+ {
+ return;
+ }
+
+ if(x + len > width)
+ {
+ len = width - x;
+ }
+
+ int a, b;
+ std::uint8_t* target = buf + (x + y * width) * 4;
+ while(len)
+ {
+ if(line[3] == 0xff)
+ {
+ const std::uint8_t* end = line;
+ while(end[3] == 0xff && end < line + len * 4)
+ {
+ end += 4;
+ }
+ auto chunk_len = end - line;
+ std::memcpy(target, line, chunk_len);
+ line += chunk_len;
+ target += chunk_len;
+ len -= chunk_len / 4;
+ continue;
+ }
+ else if(line[3] == 0)
+ {
+ // Do nothing
+ }
+ else
+ {
+ a = line[3];
+ b = target[3] * (255 - a) / 255;
+
+ target[0] = (line[0] * a + target[0] * b) / (a + b);
+ target[1] = (line[1] * a + target[1] * b) / (a + b);
+ target[2] = (line[2] * a + target[2] * b) / (a + b);
+ target[3] = (int)target[3] + line[3] * (255 - target[3]) / 255;
+ }
+
+ line += 4;
+ target += 4;
+ --len;
+ }
+}
+
+void PixelBufferAlpha::addPixel(std::size_t x, std::size_t y, const Colour& c)
+{
+ if(x >= width || y >= height)
+ {
+ return; // out of bounds
+ }
+
+ const std::uint8_t* colour = c.data();
+
+ if(colour[3] == 0)
+ {
+ return;
+ }
+
+ int a, b;
+ std::uint8_t* target = buf + (x + y * width) * 4;
+
+ if(colour[3] == 0xff)
+ {
+ std::memcpy(target, colour, 4);
+ }
+ else
+ {
+ a = colour[3];
+ b = target[3] * (255 - a) / 255;
+
+ target[0] = (colour[0] * a + target[0] * b) / (a + b);
+ target[1] = (colour[1] * a + target[1] * b) / (a + b);
+ target[2] = (colour[2] * a + target[2] * b) / (a + b);
+ target[3] = (int)target[3] + colour[3] * (255 - target[3]) / 255;
+ }
+}
+
+const Colour& PixelBufferAlpha::pixel(std::size_t x, std::size_t y) const
+{
+ static Colour c;
+ std::memcpy(c.data(), buf + (x + y * width) * 4, 4);
+ return c;
+}
+
+const std::uint8_t* PixelBufferAlpha::getLine(std::size_t x, std::size_t y) const
+{
+ return buf + (x + y * width) * 4;
+}
+
+} // GUI::
diff --git a/dggui/pixelbuffer.h b/dggui/pixelbuffer.h
new file mode 100644
index 0000000..5c11d14
--- /dev/null
+++ b/dggui/pixelbuffer.h
@@ -0,0 +1,101 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/***************************************************************************
+ * pixelbuffer.h
+ *
+ * Thu Nov 10 09:00:37 CET 2011
+ * Copyright 2011 Bent Bisballe Nyeng
+ * deva@aasimon.org
+ ****************************************************************************/
+
+/*
+ * This file is part of DrumGizmo.
+ *
+ * DrumGizmo is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * DrumGizmo is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with DrumGizmo; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+#pragma once
+
+#include "colour.h"
+
+#include <cstddef>
+#include <cstdint>
+#include <vector>
+
+#include "guievent.h"
+
+namespace GUI
+{
+
+class PixelBuffer
+{
+public:
+ PixelBuffer(std::size_t width, std::size_t height);
+ ~PixelBuffer();
+
+ void realloc(std::size_t width, std::size_t height);
+
+ void blendLine(std::size_t x, std::size_t y,
+ const std::uint8_t* line, std::size_t len);
+
+ Rect updateBuffer(std::vector<class PixelBufferAlpha*>& pixel_buffers);
+
+ std::vector<std::uint8_t> buf_data;
+ std::uint8_t* buf{nullptr};
+ std::size_t width{0};
+ std::size_t height{0};
+};
+
+class PixelBufferAlpha
+{
+public:
+ PixelBufferAlpha() = default;
+ PixelBufferAlpha(std::size_t width, std::size_t height);
+ ~PixelBufferAlpha();
+
+ void realloc(std::size_t width, std::size_t height);
+
+ void clear();
+
+ void setPixel(std::size_t x, std::size_t y, const Colour& c);
+
+ void writeLine(std::size_t x, std::size_t y,
+ const std::uint8_t* line, std::size_t len);
+ void blendLine(std::size_t x, std::size_t y,
+ const std::uint8_t* line, std::size_t len);
+
+ void addPixel(std::size_t x, std::size_t y, const Colour& c);
+
+ const Colour& pixel(std::size_t x, std::size_t y) const;
+
+ const std::uint8_t* getLine(std::size_t x, std::size_t y) const;
+
+ std::vector<std::uint8_t> buf_data;
+ std::uint8_t* buf{nullptr};
+ std::size_t width{0};
+ std::size_t height{0};
+ int x{0};
+ int y{0};
+ bool dirty{true};
+ bool visible{true};
+
+ // Add optional dirty rect that this pixelbuffer took up since it was last
+ // rendered. Make sure to update this list on resize and/or move.
+ std::size_t last_width{0};
+ std::size_t last_height{0};
+ int last_x{0};
+ int last_y{0};
+ bool has_last{false};
+};
+
+} // GUI::
diff --git a/dggui/powerbutton.cc b/dggui/powerbutton.cc
new file mode 100644
index 0000000..5bf2a2c
--- /dev/null
+++ b/dggui/powerbutton.cc
@@ -0,0 +1,88 @@
+/* -*- Mode: c++ -*- */
+/***************************************************************************
+ * powerbutton.cc
+ *
+ * Thu Mar 23 12:30:50 CET 2017
+ * Copyright 2017 André Nusser
+ * andre.nusser@googlemail.com
+ ****************************************************************************/
+
+/*
+ * This file is part of DrumGizmo.
+ *
+ * DrumGizmo is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * DrumGizmo is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with DrumGizmo; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+#include "powerbutton.h"
+
+#include "painter.h"
+
+namespace GUI
+{
+
+PowerButton::PowerButton(Widget* parent) : Toggle(parent)
+{
+}
+
+void PowerButton::setEnabled(bool enabled)
+{
+ this->enabled = enabled;
+
+ redraw();
+}
+
+void PowerButton::repaintEvent(RepaintEvent* repaintEvent)
+{
+ Painter p(*this);
+
+ // disabled
+ if(!enabled)
+ {
+ if(clicked)
+ {
+ p.drawImage(0, 0, disabled_clicked);
+ }
+ else
+ {
+ p.drawImage(0, 0, disabled);
+ }
+ return;
+ }
+
+ // enabled and on
+ if(state)
+ {
+ if(clicked)
+ {
+ p.drawImage(0, 0, on_clicked);
+ }
+ else
+ {
+ p.drawImage(0, 0, on);
+ }
+ return;
+ }
+
+ // enabled and off
+ if(clicked)
+ {
+ p.drawImage(0, 0, off_clicked);
+ }
+ else
+ {
+ p.drawImage(0, 0, off);
+ }
+}
+
+} // GUI::
diff --git a/dggui/powerbutton.h b/dggui/powerbutton.h
new file mode 100644
index 0000000..14dbeca
--- /dev/null
+++ b/dggui/powerbutton.h
@@ -0,0 +1,58 @@
+/* -*- Mode: c++ -*- */
+/***************************************************************************
+ * powerbutton.h
+ *
+ * Thu Mar 23 12:30:50 CET 2017
+ * Copyright 2017 André Nusser
+ * andre.nusser@googlemail.com
+ ****************************************************************************/
+
+/*
+ * This file is part of DrumGizmo.
+ *
+ * DrumGizmo is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * DrumGizmo is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with DrumGizmo; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+#pragma once
+
+#include "texture.h"
+#include "toggle.h"
+
+namespace GUI
+{
+
+class PowerButton : public Toggle
+{
+public:
+ PowerButton(Widget* parent);
+ virtual ~PowerButton() = default;
+
+ void setEnabled(bool enabled);
+
+protected:
+ // From Widget:
+ virtual void repaintEvent(RepaintEvent* repaintEvent) override;
+
+ bool enabled = true;
+
+private:
+ Texture on{getImageCache(), ":resources/bypass_button.png", 32, 0, 16, 16};
+ Texture on_clicked{getImageCache(), ":resources/bypass_button.png", 48, 0, 16, 16};
+ Texture off{getImageCache(), ":resources/bypass_button.png", 0, 0, 16, 16};
+ Texture off_clicked{getImageCache(), ":resources/bypass_button.png", 16, 0, 16, 16};
+ Texture disabled{getImageCache(), ":resources/bypass_button.png", 64, 0, 16, 16};
+ Texture disabled_clicked{getImageCache(), ":resources/bypass_button.png", 80, 0, 16, 16};
+};
+
+} // GUI::
diff --git a/dggui/progressbar.cc b/dggui/progressbar.cc
new file mode 100644
index 0000000..dd3e130
--- /dev/null
+++ b/dggui/progressbar.cc
@@ -0,0 +1,104 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/***************************************************************************
+ * progressbar.cc
+ *
+ * Fri Mar 22 22:07:57 CET 2013
+ * Copyright 2013 Bent Bisballe Nyeng
+ * deva@aasimon.org
+ ****************************************************************************/
+
+/*
+ * This file is part of DrumGizmo.
+ *
+ * DrumGizmo is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * DrumGizmo is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with DrumGizmo; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+#include "progressbar.h"
+
+namespace GUI
+{
+
+ProgressBar::ProgressBar(Widget *parent)
+ : Widget(parent)
+{
+}
+
+ProgressBar::~ProgressBar()
+{
+}
+
+void ProgressBar::setState(ProgressBarState state)
+{
+ if(this->state != state)
+ {
+ this->state = state;
+ redraw();
+ }
+}
+
+void ProgressBar::setTotal(std::size_t total)
+{
+ if(this->total != total)
+ {
+ this->total = total;
+ redraw();
+ }
+}
+
+void ProgressBar::setValue(std::size_t value)
+{
+ if(this->value != value)
+ {
+ this->value = value;
+ redraw();
+ }
+}
+
+void ProgressBar::repaintEvent(RepaintEvent* repaintEvent)
+{
+ Painter p(*this);
+
+ float progress = 0.0f;
+ if(total != 0)
+ {
+ progress = (float)value / (float)total;
+ }
+
+ int brd = 4;
+ int val = (width() - (2 * brd)) * progress;
+
+ bar_bg.setSize(width(), height());
+ p.drawImage(0, 0, bar_bg);
+
+ switch(state)
+ {
+ case ProgressBarState::Red:
+ bar_red.setSize(val, height());
+ p.drawImage(brd, 0, bar_red);
+ break;
+ case ProgressBarState::Green:
+ bar_green.setSize(val, height());
+ p.drawImage(brd, 0, bar_green);
+ break;
+ case ProgressBarState::Blue:
+ bar_blue.setSize(val, height());
+ p.drawImage(brd, 0, bar_blue);
+ break;
+ case ProgressBarState::Off:
+ return;
+ }
+
+}
+
+} // GUI::
diff --git a/dggui/progressbar.h b/dggui/progressbar.h
new file mode 100644
index 0000000..76cc6fd
--- /dev/null
+++ b/dggui/progressbar.h
@@ -0,0 +1,89 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/***************************************************************************
+ * progressbar.h
+ *
+ * Fri Mar 22 22:07:57 CET 2013
+ * Copyright 2013 Bent Bisballe Nyeng
+ * deva@aasimon.org
+ ****************************************************************************/
+
+/*
+ * This file is part of DrumGizmo.
+ *
+ * DrumGizmo is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * DrumGizmo is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with DrumGizmo; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+#pragma once
+
+#include "widget.h"
+
+#include "guievent.h"
+#include "painter.h"
+#include "texturedbox.h"
+
+namespace GUI
+{
+
+enum class ProgressBarState
+{
+ Red,
+ Green,
+ Blue,
+ Off
+};
+
+class ProgressBar
+ : public Widget
+{
+public:
+ ProgressBar(Widget* parent);
+ virtual ~ProgressBar();
+
+ void setTotal(std::size_t total);
+ void setValue(std::size_t value);
+
+ void setState(ProgressBarState state);
+
+protected:
+ // From Widget:
+ virtual void repaintEvent(RepaintEvent* repaintEvent) override;
+
+private:
+ ProgressBarState state{ProgressBarState::Blue};
+
+ TexturedBox bar_bg{getImageCache(), ":resources/progress.png",
+ 0, 0, // atlas offset (x, y)
+ 6, 1, 6, // dx1, dx2, dx3
+ 11, 0, 0}; // dy1, dy2, dy3
+
+ TexturedBox bar_red{getImageCache(), ":resources/progress.png",
+ 13, 0, // atlas offset (x, y)
+ 2, 1, 2, // dx1, dx2, dx3
+ 11, 0, 0}; // dy1, dy2, dy3
+
+ TexturedBox bar_green{getImageCache(), ":resources/progress.png",
+ 18, 0, // atlas offset (x, y)
+ 2, 1, 2, // dx1, dx2, dx3
+ 11, 0, 0}; // dy1, dy2, dy3
+
+ TexturedBox bar_blue{getImageCache(), ":resources/progress.png",
+ 23, 0, // atlas offset (x, y)
+ 2, 1, 2, // dx1, dx2, dx3
+ 11, 0, 0}; // dy1, dy2, dy3
+
+ std::size_t total{0};
+ std::size_t value{0};
+};
+
+} // GUI::
diff --git a/dggui/rc_data.cc b/dggui/rc_data.cc
new file mode 100644
index 0000000..18e3cd5
--- /dev/null
+++ b/dggui/rc_data.cc
@@ -0,0 +1,29 @@
+/* -*- Mode: c++ -*- */
+/***************************************************************************
+ * rc_data.cc
+ *
+ * Wed Dec 23 09:51:20 CET 2020
+ * Copyright 2020 Bent Bisballe Nyeng
+ * deva@aasimon.org
+ ****************************************************************************/
+
+/*
+ * This file is part of DrumGizmo.
+ *
+ * DrumGizmo is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * DrumGizmo is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with DrumGizmo; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+#include "resource_data.h"
+
+const rc_data_t* rc_data __attribute__((__weak__)) = nullptr;
diff --git a/dggui/rcgentool.cc b/dggui/rcgentool.cc
new file mode 100644
index 0000000..c0ba0db
--- /dev/null
+++ b/dggui/rcgentool.cc
@@ -0,0 +1,192 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/***************************************************************************
+ * rcgen.cc
+ *
+ * Sun Mar 17 20:27:17 CET 2013
+ * Copyright 2013 Bent Bisballe Nyeng
+ * deva@aasimon.org
+ ****************************************************************************/
+
+/*
+ * This file is part of DrumGizmo.
+ *
+ * DrumGizmo is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * DrumGizmo is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with DrumGizmo; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+#include <stdio.h>
+#include <string>
+#include <unistd.h>
+#include <sstream>
+
+#include <getoptpp.hpp>
+
+std::string usage(const std::string& name, bool brief = false)
+{
+ std::ostringstream output;
+ output <<
+ "Usage: " << name << " [options]\n";
+ if(!brief)
+ {
+ output <<
+ "\n"
+ "Create resource file from list of input files.\n"
+ "\n";
+ }
+ return output.str();
+}
+
+int main(int argc, char *argv[])
+{
+ bool verbose{false};
+ std::vector<std::string> stripPrefixes;
+ std::string dirRoot;
+ std::string outfile;
+
+ dg::Options opt;
+
+ opt.add("strip-path", required_argument, 's',
+ "Strip supplied path prefix from resource names.",
+ [&]()
+ {
+ stripPrefixes.push_back(optarg);
+ return 0;
+ });
+
+ opt.add("dir-root", required_argument, 'd',
+ "Change to supplied root dir before reading files.",
+ [&]()
+ {
+ dirRoot = optarg;
+ return 0;
+ });
+
+ opt.add("output", required_argument, 'o',
+ "Write output to specificed file, defaults to stdout.",
+ [&]()
+ {
+ outfile = optarg;
+ return 0;
+ });
+
+ opt.add("verbose", no_argument, 'v',
+ "Print verbose output during processing.",
+ [&]()
+ {
+ verbose = true;
+ return 0;
+ });
+
+ opt.add("help", no_argument, 'h',
+ "Print this message and exit.",
+ [&]()
+ {
+ std::cout << usage(argv[0]);
+ std::cout << "Options:\n";
+ opt.help();
+ exit(0);
+ return 0;
+ });
+
+ if(opt.process(argc, argv) != 0)
+ {
+ return 1;
+ }
+
+ FILE* out = stdout;
+ if(!outfile.empty())
+ {
+ out = fopen(outfile.data(), "wb");
+ if(!out)
+ {
+ fprintf(stderr, "Could not write to file '%s' - quitting\n",
+ outfile.data());
+ return 1;
+
+ }
+ }
+
+ fprintf(out, "/* This file is autogenerated by rcgen. Do not modify! */\n");
+ fprintf(out, "#include <dggui/resource_data.h>\n");
+ fprintf(out, "\n");
+ fprintf(out, "const rc_data_t rc_dataX[] =\n");
+ fprintf(out, "{\n");
+
+ if(!dirRoot.empty())
+ {
+ if(verbose)
+ {
+ fprintf(stderr, "Change to dir: %s\n", dirRoot.data());
+ }
+
+ if(chdir(dirRoot.data()))
+ {
+ return 1;
+ }
+ }
+
+ for(const auto& arg : opt.arguments())
+ {
+ std::string resourceName = arg;
+ for(const auto& stripPrefix : stripPrefixes)
+ {
+ if(stripPrefix == resourceName.substr(0, stripPrefix.length()))
+ {
+ resourceName = resourceName.substr(stripPrefix.length());
+ break;
+ }
+ }
+
+ fprintf(out, " {\n \":%s\", ", resourceName.data());
+
+ if(verbose)
+ {
+ fprintf(stderr, "Process: %s\n", arg.data());
+ }
+
+ std::string data;
+ FILE *fp = fopen(arg.data(), "rb");
+ if(!fp)
+ {
+ fprintf(stderr, "Could not read file '%s' - quitting\n", arg.data());
+ return 1;
+ }
+
+ char buf[32];
+ while(!feof(fp))
+ {
+ std::size_t sz = fread(buf, 1, sizeof(buf), fp);
+ data.append(buf, sz);
+ }
+ fclose(fp);
+
+ fprintf(out, "%d,\n \"", (int)data.length());
+ for(std::size_t j = 0; j < data.length(); ++j)
+ {
+ if((j != 0) && (j % 16) == 0)
+ {
+ fprintf(out, "\"\n \"");
+ }
+ fprintf(out, "\\%o", (unsigned char)data[j]);
+ }
+
+ fprintf(out, "\"\n },\n");
+ }
+
+ fprintf(out, " { \"\", 0, 0 }\n");
+ fprintf(out, "};\n");
+
+ fprintf(out, "\nconst rc_data_t* rc_data = rc_dataX;\n");
+
+ return 0;
+}
diff --git a/dggui/resource.cc b/dggui/resource.cc
new file mode 100644
index 0000000..52aca0a
--- /dev/null
+++ b/dggui/resource.cc
@@ -0,0 +1,194 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/***************************************************************************
+ * resource.cc
+ *
+ * Sun Mar 17 19:38:04 CET 2013
+ * Copyright 2013 Bent Bisballe Nyeng
+ * deva@aasimon.org
+ ****************************************************************************/
+
+/*
+ * This file is part of DrumGizmo.
+ *
+ * DrumGizmo is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * DrumGizmo is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with DrumGizmo; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+#include "resource.h"
+
+#include <hugin.hpp>
+#include <cstdio>
+#include <climits>
+
+#include <platform.h>
+
+#if DG_PLATFORM != DG_PLATFORM_WINDOWS
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#endif
+
+// rcgen generated file containing rc_data declaration.
+#include "resource_data.h"
+
+extern const rc_data_t* rc_data;
+
+namespace GUI
+{
+
+// TODO: Replace with std::filesystem::is_regular_file once we update the
+// compiler to require C++17
+static bool pathIsFile(const std::string& path)
+{
+#if DG_PLATFORM == DG_PLATFORM_WINDOWS
+ return (GetFileAttributesA(path.data()) & FILE_ATTRIBUTE_DIRECTORY) == 0;
+#else
+ struct stat s;
+ if(stat(path.data(), &s) != 0)
+ {
+ return false; // error
+ }
+
+ return (s.st_mode & S_IFREG) != 0; // s.st_mode & S_IFDIR => dir
+#endif
+}
+
+// Internal resources start with a colon.
+static bool nameIsInternal(const std::string& name)
+{
+ return name.size() && (name[0] == ':');
+}
+
+Resource::Resource(const std::string& name)
+{
+ isValid = false;
+
+ if(nameIsInternal(name))
+ {
+ // Use internal resource:
+
+ // Find internal resource in rc_data.
+ const rc_data_t* p = rc_data;
+ while(p && *p->name) // last entry in rc_data has the name := ""
+ {
+ if(name == p->name)
+ {
+ internalData = p->data;
+ internalSize = p->size;
+ break;
+ }
+ ++p;
+ }
+
+ // We did not find the named resource.
+ if(internalData == nullptr)
+ {
+ ERR(rc, "Could not find '%s'\n", name.c_str());
+ return;
+ }
+
+ isInternal = true;
+ }
+ else
+ {
+ if(!pathIsFile(name))
+ {
+ return;
+ }
+
+ // Read from file:
+ std::FILE *fp = std::fopen(name.data(), "rb");
+ if(!fp)
+ {
+ return;
+ }
+
+ // Get the file size
+ if(std::fseek(fp, 0, SEEK_END) == -1)
+ {
+ std::fclose(fp);
+ return;
+ }
+
+ long filesize = std::ftell(fp);
+
+ // Apparently fseek doesn't fail if fp points to a directory that has been
+ // opened (which doesn't fail either!!) and ftell will then fail by either
+ // returning -1 or LONG_MAX
+ if(filesize == -1L || filesize == LONG_MAX)
+ {
+ std::fclose(fp);
+ return;
+ }
+
+ // Reserve space in the string for the data.
+ externalData.reserve(filesize);
+
+ // Rewind and read...
+ std::rewind(fp);
+
+ char buffer[32];
+ while(!std::feof(fp))
+ {
+ size_t size = std::fread(buffer, 1, sizeof(buffer), fp);
+ externalData.append(buffer, size);
+ }
+
+ std::fclose(fp);
+
+ isInternal = false;
+ }
+
+ isValid = true;
+}
+
+const char *Resource::data()
+{
+ if(isValid == false)
+ {
+ return nullptr;
+ }
+
+ if(isInternal)
+ {
+ return internalData;
+ }
+ else
+ {
+ return externalData.data();
+ }
+}
+
+size_t Resource::size()
+{
+ if(isValid == false)
+ {
+ return 0;
+ }
+
+ if(isInternal)
+ {
+ return internalSize;
+ }
+ else
+ {
+ return externalData.length();
+ }
+}
+
+bool Resource::valid()
+{
+ return isValid;
+}
+
+} // GUI::
diff --git a/dggui/resource.h b/dggui/resource.h
new file mode 100644
index 0000000..5f7b3b4
--- /dev/null
+++ b/dggui/resource.h
@@ -0,0 +1,50 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/***************************************************************************
+ * resource.h
+ *
+ * Sun Mar 17 19:38:03 CET 2013
+ * Copyright 2013 Bent Bisballe Nyeng
+ * deva@aasimon.org
+ ****************************************************************************/
+
+/*
+ * This file is part of DrumGizmo.
+ *
+ * DrumGizmo is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * DrumGizmo is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with DrumGizmo; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+#pragma once
+
+#include <string>
+
+namespace GUI {
+
+class Resource {
+public:
+ Resource(const std::string& name);
+
+ const char* data();
+ size_t size();
+
+ bool valid();
+
+protected:
+ std::string externalData;
+ bool isValid{false};
+ bool isInternal{false};
+ const char *internalData{nullptr};
+ size_t internalSize{0};
+};
+
+} // GUI::
diff --git a/dggui/resource_data.h b/dggui/resource_data.h
new file mode 100644
index 0000000..d685e13
--- /dev/null
+++ b/dggui/resource_data.h
@@ -0,0 +1,33 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/***************************************************************************
+ * resource_data.h
+ *
+ * Sun Mar 17 20:25:24 CET 2013
+ * Copyright 2013 Bent Bisballe Nyeng
+ * deva@aasimon.org
+ ****************************************************************************/
+
+/*
+ * This file is part of DrumGizmo.
+ *
+ * DrumGizmo is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * DrumGizmo is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with DrumGizmo; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+#pragma once
+
+typedef struct {
+ const char *name;
+ unsigned int size;
+ const char *data;
+} rc_data_t;
diff --git a/dggui/scrollbar.cc b/dggui/scrollbar.cc
new file mode 100644
index 0000000..9a17d1d
--- /dev/null
+++ b/dggui/scrollbar.cc
@@ -0,0 +1,214 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/***************************************************************************
+ * scrollbar.cc
+ *
+ * Sun Apr 14 12:54:58 CEST 2013
+ * Copyright 2013 Bent Bisballe Nyeng
+ * deva@aasimon.org
+ ****************************************************************************/
+
+/*
+ * This file is part of DrumGizmo.
+ *
+ * DrumGizmo is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * DrumGizmo is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with DrumGizmo; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+#include "scrollbar.h"
+
+#include <hugin.hpp>
+
+#include "painter.h"
+
+namespace GUI {
+
+ScrollBar::ScrollBar(Widget *parent)
+ : Widget(parent)
+{
+}
+
+void ScrollBar::setRange(int r)
+{
+ rangeValue = r;
+ setValue(value());
+ redraw();
+}
+
+int ScrollBar::range()
+{
+ return rangeValue;
+}
+
+void ScrollBar::setMaximum(int m)
+{
+ maxValue = m;
+ if(maxValue < rangeValue)
+ {
+ rangeValue = maxValue;
+ }
+ setValue(value());
+ redraw();
+}
+
+int ScrollBar::maximum()
+{
+ return maxValue;
+}
+
+void ScrollBar::addValue(int delta)
+{
+ setValue(value() + delta);
+}
+
+void ScrollBar::setValue(int value)
+{
+ if(value > (maxValue - rangeValue))
+ {
+ value = maxValue - rangeValue;
+ }
+
+ if(value < 0)
+ {
+ value = 0;
+ }
+
+ if(currentValue == value)
+ {
+ return;
+ }
+
+ currentValue = value;
+
+ valueChangeNotifier(value);
+
+ redraw();
+}
+
+int ScrollBar::value()
+{
+ return currentValue;
+}
+
+//! Draw an up/down arrow at (x,y) with the bounding box size (w,h)
+//! If h is negative the arrow will point down, if positive it will point up.
+static void drawArrow(Painter &p, int x, int y, int w, int h)
+{
+ if(h < 0)
+ {
+ y -= h;
+ }
+
+ p.drawLine(x, y, x + (w / 2), y + h);
+ p.drawLine(x + (w / 2), y + h, x + w, y);
+
+ ++y;
+ p.drawLine(x, y, x + (w / 2), y + h);
+ p.drawLine(x + (w / 2), y + h, x + w, y);
+}
+
+void ScrollBar::repaintEvent(RepaintEvent* repaintEvent)
+{
+ Painter p(*this);
+
+ p.clear();
+
+ p.drawImageStretched(0, 0, bg_img, width(), height());
+
+ p.setColour(Colour(183.0f/255.0f, 219.0f/255.0f, 255.0f/255.0f, 1.0f));
+ if(!maxValue)
+ {
+ return;
+ }
+
+ {
+ int h = height() - 2 * width() - 3;
+ int offset = width() + 2;
+
+ int y_val1 = (currentValue * h) / maxValue;
+ int y_val2 = ((currentValue + rangeValue) * h) / maxValue - 1;
+
+ p.drawFilledRectangle(2, y_val1 + offset, width() - 1, y_val2 + offset);
+ }
+
+ p.drawLine(0, 0, 0, height());
+
+ drawArrow(p, width()/4, width()/4, width()/2, -1 * (width()/3));
+ p.drawLine(0, width(), width(), width());
+
+ drawArrow(p, width()/4, height() - width() + width()/4, width()/2, width()/3);
+ p.drawLine(0, height() - width(), width(), height() - width());
+}
+
+void ScrollBar::scrollEvent(ScrollEvent* scrollEvent)
+{
+ setValue(value() + scrollEvent->delta);
+}
+
+void ScrollBar::mouseMoveEvent(MouseMoveEvent* mouseMoveEvent)
+{
+ if(!dragging)
+ {
+ return;
+ }
+
+ float delta = yOffset - mouseMoveEvent->y;
+
+ int h = height() - 2 * width() - 3;
+ delta /= (float)h / (float)maxValue;
+
+ int newval = valueOffset - delta;
+ if(newval != value())
+ {
+ setValue(newval);
+ }
+}
+
+void ScrollBar::buttonEvent(ButtonEvent* buttonEvent)
+{
+ // Ignore everything except left clicks.
+ if(buttonEvent->button != MouseButton::left)
+ {
+ return;
+ }
+
+ if((buttonEvent->y < (int)width()) && buttonEvent->y > 0)
+ {
+ if(buttonEvent->direction == Direction::down)
+ {
+ addValue(-1);
+ }
+
+ return;
+ }
+
+ if((buttonEvent->y > ((int)height() - (int)width())) &&
+ (buttonEvent->y < (int)height()))
+ {
+ if(buttonEvent->direction == Direction::down)
+ {
+ addValue(1);
+ }
+
+ return;
+ }
+
+ if(buttonEvent->direction == Direction::down)
+ {
+ yOffset = buttonEvent->y;
+ valueOffset = value();
+ }
+
+ dragging = (buttonEvent->direction == Direction::down);
+}
+
+} // GUI::
diff --git a/dggui/scrollbar.h b/dggui/scrollbar.h
new file mode 100644
index 0000000..5e60673
--- /dev/null
+++ b/dggui/scrollbar.h
@@ -0,0 +1,74 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/***************************************************************************
+ * scrollbar.h
+ *
+ * Sun Apr 14 12:54:58 CEST 2013
+ * Copyright 2013 Bent Bisballe Nyeng
+ * deva@aasimon.org
+ ****************************************************************************/
+
+/*
+ * This file is part of DrumGizmo.
+ *
+ * DrumGizmo is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * DrumGizmo is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with DrumGizmo; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+#pragma once
+
+#include <limits>
+
+#include "widget.h"
+#include "texture.h"
+#include "notifier.h"
+
+namespace GUI {
+
+class ScrollBar : public Widget {
+ friend class ListBoxBasic;
+public:
+ ScrollBar(Widget *parent);
+
+ void setRange(int range);
+ int range();
+
+ void setMaximum(int max);
+ int maximum();
+
+ void addValue(int delta);
+ void setValue(int value);
+ int value();
+
+ Notifier<int> valueChangeNotifier; // (int value)
+
+protected:
+ // From Widget:
+ bool catchMouse() override { return true; }
+ void scrollEvent(ScrollEvent* scrollEvent) override;
+ void repaintEvent(RepaintEvent* repaintEvent) override;
+ void buttonEvent(ButtonEvent* buttonEvent) override;
+ void mouseMoveEvent(MouseMoveEvent* mouseMoveEvent) override;
+
+private:
+ int maxValue{100};
+ int currentValue{0};
+ int rangeValue{10};
+
+ int yOffset{0};
+ int valueOffset{0};
+ bool dragging{false};
+
+ Texture bg_img{getImageCache(), ":resources/widget.png", 7, 7, 1, 63};
+};
+
+} // GUI::
diff --git a/dggui/slider.cc b/dggui/slider.cc
new file mode 100644
index 0000000..15a82b3
--- /dev/null
+++ b/dggui/slider.cc
@@ -0,0 +1,217 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/***************************************************************************
+ * slider.cc
+ *
+ * Sat Nov 26 18:10:22 CET 2011
+ * Copyright 2011 Bent Bisballe Nyeng
+ * deva@aasimon.org
+ ****************************************************************************/
+
+/*
+ * This file is part of DrumGizmo.
+ *
+ * DrumGizmo is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * DrumGizmo is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with DrumGizmo; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+#include "slider.h"
+
+#include "painter.h"
+
+#include <hugin.hpp>
+#include <stdio.h>
+
+namespace GUI
+{
+
+Slider::Slider(Widget* parent) : Widget(parent)
+{
+ state = State::up;
+
+ current_value = 0.0;
+ maximum = 1.0;
+ minimum = 0.0;
+}
+
+void Slider::setValue(float new_value)
+{
+ current_value = new_value;
+ if (current_value < 0.)
+ {
+ current_value = 0.;
+ }
+ else if (current_value > 1.0) {
+ current_value = 1.0;
+ }
+
+ redraw();
+ clickNotifier();
+ valueChangedNotifier(current_value);
+}
+
+float Slider::value() const
+{
+ return current_value;
+}
+
+void Slider::setColour(Colour colour)
+{
+ switch (colour) {
+ case Colour::Green:
+ active_inner_bar = &inner_bar_green;
+ break;
+ case Colour::Red:
+ active_inner_bar = &inner_bar_red;
+ break;
+ case Colour::Blue:
+ active_inner_bar = &inner_bar_blue;
+ break;
+ case Colour::Yellow:
+ active_inner_bar = &inner_bar_yellow;
+ break;
+ case Colour::Purple:
+ active_inner_bar = &inner_bar_purple;
+ break;
+ case Colour::Grey:
+ active_inner_bar = &inner_bar_grey;
+ break;
+ }
+
+ if (enabled) { inner_bar = active_inner_bar; }
+}
+
+void Slider::setEnabled(bool enabled)
+{
+ this->enabled = enabled;
+
+ if (enabled) {
+ inner_bar = active_inner_bar;
+ }
+ else {
+ active_inner_bar = inner_bar;
+ inner_bar = &inner_bar_light_grey;
+ }
+
+ redraw();
+}
+
+void Slider::repaintEvent(RepaintEvent* repaintEvent)
+{
+ Painter p(*this);
+
+ auto inner_offset = (current_value / maximum) * getControlWidth();
+ auto button_x = button_offset + inner_offset - (button.width() / 2);
+ auto button_y = (height() - button.height()) / 2;
+
+ // draw bar
+ bar.setSize(width(), height());
+ p.drawImage(0, 0, bar);
+
+ // draw inner bar
+ inner_bar->setSize(button_x - bar_boundary, height() - 2 * bar_boundary);
+ p.drawImage(bar_boundary, bar_boundary, *inner_bar);
+
+ // draw button
+ p.drawImage(button_x, button_y, button);
+}
+
+void Slider::buttonEvent(ButtonEvent* buttonEvent)
+{
+ // Ignore everything except left clicks.
+ if(!enabled || buttonEvent->button != MouseButton::left)
+ {
+ return;
+ }
+
+ if(buttonEvent->direction == Direction::down)
+ {
+ state = State::down;
+ recomputeCurrentValue(buttonEvent->x);
+
+ redraw();
+ clickNotifier();
+ valueChangedNotifier(current_value);
+ }
+
+ if(buttonEvent->direction == Direction::up)
+ {
+ state = State::up;
+ recomputeCurrentValue(buttonEvent->x);
+
+ redraw();
+ clickNotifier();
+ valueChangedNotifier(current_value);
+ }
+}
+
+void Slider::mouseMoveEvent(MouseMoveEvent* mouseMoveEvent)
+{
+ if(state == State::down)
+ {
+ recomputeCurrentValue(mouseMoveEvent->x);
+
+ redraw();
+ clickNotifier();
+ valueChangedNotifier(current_value);
+ }
+}
+
+void Slider::scrollEvent(ScrollEvent* scrollEvent)
+{
+ if (!enabled) { return; }
+
+ current_value -= scrollEvent->delta/(float)getControlWidth();
+ if (current_value < 0.)
+ {
+ current_value = 0.;
+ }
+ else if (current_value > 1.0) {
+ current_value = 1.0;
+ }
+
+ redraw();
+ clickNotifier();
+ valueChangedNotifier(current_value);
+}
+
+std::size_t Slider::getControlWidth() const
+{
+ if(width() < 2 * button_offset)
+ {
+ return 0;
+ }
+
+ return width() - 2 * button_offset;
+}
+
+void Slider::recomputeCurrentValue(float x)
+{
+ if(x < button_offset)
+ {
+ current_value = 0;
+ }
+ else
+ {
+ current_value = (x - button_offset) / getControlWidth();
+ }
+
+ if (current_value < 0.)
+ {
+ current_value = 0.;
+ }
+ else if (current_value > 1.0) {
+ current_value = 1.0;
+ }
+}
+
+} // GUI::
diff --git a/dggui/slider.h b/dggui/slider.h
new file mode 100644
index 0000000..90905fd
--- /dev/null
+++ b/dggui/slider.h
@@ -0,0 +1,153 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/***************************************************************************
+ * slider.h
+ *
+ * Sat Nov 26 18:10:22 CET 2011
+ * Copyright 2011 Bent Bisballe Nyeng
+ * deva@aasimon.org
+ ****************************************************************************/
+
+/*
+ * This file is part of DrumGizmo.
+ *
+ * DrumGizmo is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * DrumGizmo is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with DrumGizmo; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+#pragma once
+
+#include "texture.h"
+#include "texturedbox.h"
+#include "widget.h"
+
+namespace GUI
+{
+
+class Slider
+ : public Widget
+{
+public:
+ Slider(Widget* parent);
+ virtual ~Slider() = default;
+
+ // From Widget:
+ bool catchMouse() override
+ {
+ return true;
+ }
+ bool isFocusable() override
+ {
+ return true;
+ }
+
+ void setValue(float new_value);
+ float value() const;
+
+ enum class Colour { Green, Red, Blue, Yellow, Purple, Grey };
+ // Changes the colour of the inner bar
+ void setColour(Colour colour);
+ void setEnabled(bool enabled);
+
+ Notifier<> clickNotifier;
+ Notifier<float> valueChangedNotifier; // (float value)
+
+protected:
+ virtual void repaintEvent(RepaintEvent* repaintEvent) override;
+ virtual void buttonEvent(ButtonEvent* buttonEvent) override;
+ virtual void mouseMoveEvent(MouseMoveEvent* mouseMoveEvent) override;
+ virtual void scrollEvent(ScrollEvent* scrollEvent) override;
+
+ bool enabled = true;;
+
+private:
+ enum class State
+ {
+ up,
+ down
+ };
+
+ float current_value;
+ float maximum;
+ float minimum;
+
+ State state;
+
+ TexturedBox bar{getImageCache(), ":resources/slider.png",
+ 0, 0, // atlas offset (x, y)
+ 7, 1, 7, // dx1, dx2, dx3
+ 7, 1, 7 // dy1, dy2, dy3
+ };
+ Texture button{
+ getImageCache(), ":resources/slider.png", 15, 0, // atlas offset (x, y)
+ 15, 15 // width, height
+ };
+
+ TexturedBox inner_bar_green{getImageCache(), ":resources/slider.png",
+ 30, 0, // atlas offset (x, y)
+ 2, 1, 2, // dx1, dx2, dx3
+ 2, 1, 2 // dy1, dy2, dy3
+ };
+ TexturedBox inner_bar_red{getImageCache(), ":resources/slider.png",
+ 30, 5, // atlas offset (x, y)
+ 2, 1, 2, // dx1, dx2, dx3
+ 2, 1, 2 // dy1, dy2, dy3
+ };
+ TexturedBox inner_bar_blue{getImageCache(), ":resources/slider.png",
+ 30, 10, // atlas offset (x, y)
+ 2, 1, 2, // dx1, dx2, dx3
+ 2, 1, 2 // dy1, dy2, dy3
+ };
+ TexturedBox inner_bar_yellow{getImageCache(), ":resources/slider.png",
+ 35, 0, // atlas offset (x, y)
+ 2, 1, 2, // dx1, dx2, dx3
+ 2, 1, 2 // dy1, dy2, dy3
+ };
+ TexturedBox inner_bar_purple{getImageCache(), ":resources/slider.png",
+ 35, 5, // atlas offset (x, y)
+ 2, 1, 2, // dx1, dx2, dx3
+ 2, 1, 2 // dy1, dy2, dy3
+ };
+ TexturedBox inner_bar_grey{getImageCache(), ":resources/slider.png",
+ 35, 10, // atlas offset (x, y)
+ 2, 1, 2, // dx1, dx2, dx3
+ 2, 1, 2 // dy1, dy2, dy3
+ };
+ TexturedBox inner_bar_turquoise{getImageCache(), ":resources/slider.png",
+ 40, 0, // atlas offset (x, y)
+ 2, 1, 2, // dx1, dx2, dx3
+ 2, 1, 2 // dy1, dy2, dy3
+ };
+ TexturedBox inner_bar_orange{getImageCache(), ":resources/slider.png",
+ 40, 5, // atlas offset (x, y)
+ 2, 1, 2, // dx1, dx2, dx3
+ 2, 1, 2 // dy1, dy2, dy3
+ };
+ TexturedBox inner_bar_light_grey{getImageCache(), ":resources/slider.png",
+ 40, 10, // atlas offset (x, y)
+ 2, 1, 2, // dx1, dx2, dx3
+ 2, 1, 2 // dy1, dy2, dy3
+ };
+
+ // This points to the inner_bar_* of the current color.
+ // It should never be a nullptr!
+ TexturedBox* inner_bar{&inner_bar_blue};
+ TexturedBox* active_inner_bar = inner_bar;
+
+ std::size_t bar_boundary{5};
+ std::size_t button_offset{7};
+
+ std::size_t getControlWidth() const;
+ void recomputeCurrentValue(float x);
+};
+
+} // GUI::
diff --git a/dggui/stackedwidget.cc b/dggui/stackedwidget.cc
new file mode 100644
index 0000000..05f8f3c
--- /dev/null
+++ b/dggui/stackedwidget.cc
@@ -0,0 +1,151 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/***************************************************************************
+ * stackedwidget.cc
+ *
+ * Mon Nov 21 19:36:49 CET 2016
+ * Copyright 2016 Bent Bisballe Nyeng
+ * deva@aasimon.org
+ ****************************************************************************/
+
+/*
+ * This file is part of DrumGizmo.
+ *
+ * DrumGizmo is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * DrumGizmo is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with DrumGizmo; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+#include "stackedwidget.h"
+
+namespace GUI
+{
+
+StackedWidget::StackedWidget(Widget *parent)
+ : Widget(parent)
+{
+ CONNECT(this, sizeChangeNotifier, this, &StackedWidget::sizeChanged);
+}
+
+StackedWidget::~StackedWidget()
+{
+}
+
+void StackedWidget::addWidget(Widget *widget)
+{
+ widgets.push_back(widget);
+ widget->reparent(this);
+
+ if(currentWidget == nullptr)
+ {
+ setCurrentWidget(widget);
+ }
+ else
+ {
+ widget->setVisible(false);
+ }
+}
+
+void StackedWidget::removeWidget(Widget *widget)
+{
+ if(widget == currentWidget)
+ {
+ setCurrentWidget(nullptr);
+ }
+
+ widgets.remove(widget);
+}
+
+Widget *StackedWidget::getCurrentWidget() const
+{
+ return currentWidget;
+}
+
+void StackedWidget::setCurrentWidget(Widget *widget)
+{
+ if(widget == currentWidget)
+ {
+ return;
+ }
+
+ if(currentWidget)
+ {
+ currentWidget->setVisible(false);
+ }
+
+ currentWidget = widget;
+
+ if(currentWidget)
+ {
+ currentWidget->move(0, 0);
+ currentWidget->resize(width(), height());
+ currentWidget->setVisible(true);
+ }
+
+ currentChanged(currentWidget);
+}
+
+Widget* StackedWidget::getWidgetAfter(Widget* widget)
+{
+ bool found_it{false};
+
+ for(auto w : widgets)
+ {
+ if(found_it)
+ {
+ return w;
+ }
+
+ if(w == widget)
+ {
+ found_it = true;
+ }
+ }
+
+ if(found_it)
+ {
+ // widget was the last in the list.
+ return nullptr;
+ }
+
+ // The Widget pointed to by 'widget' was not in the list...
+ return nullptr;
+}
+
+Widget* StackedWidget::getWidgetBefore(Widget* widget)
+{
+ Widget* last{nullptr};
+
+ for(auto w : widgets)
+ {
+ if(w == widget)
+ {
+ return last;
+ }
+
+ last = w;
+ }
+
+ // The Widget pointed to by 'widget' was not in the list...
+ return nullptr;
+}
+
+void StackedWidget::sizeChanged(int width, int height)
+{
+ // Propagate size change to child:
+ if(currentWidget)
+ {
+ currentWidget->move(0, 0);
+ currentWidget->resize(width, height);
+ }
+}
+
+} // GUI::
diff --git a/dggui/stackedwidget.h b/dggui/stackedwidget.h
new file mode 100644
index 0000000..24213f7
--- /dev/null
+++ b/dggui/stackedwidget.h
@@ -0,0 +1,81 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/***************************************************************************
+ * stackedwidget.h
+ *
+ * Mon Nov 21 19:36:49 CET 2016
+ * Copyright 2016 Bent Bisballe Nyeng
+ * deva@aasimon.org
+ ****************************************************************************/
+
+/*
+ * This file is part of DrumGizmo.
+ *
+ * DrumGizmo is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * DrumGizmo is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with DrumGizmo; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+#pragma once
+
+#include <list>
+
+#include "widget.h"
+#include "notifier.h"
+
+namespace GUI
+{
+
+//! A StackedWidget is a widget containing a list of widgets but only showing
+//! one of them at a time.
+//! It is be used to implement a TabWidget but can also be used for other
+//! purposes.
+class StackedWidget
+ : public Widget
+{
+public:
+ StackedWidget(Widget* parent);
+ ~StackedWidget();
+
+ //! Add a widget to the stack.
+ void addWidget(Widget* widget);
+
+ //! Remove a widget from the stack.
+ void removeWidget(Widget* widget);
+
+ //! Get currently visible widget.
+ Widget* getCurrentWidget() const;
+
+ //! Show widget. Hide all the others.
+ //! If widget is not in the stack nothing happens.
+ void setCurrentWidget(Widget* widget);
+
+ //! Returns a pointer to the Widget after the one referenced by 'widget' or
+ //! nullptr if 'widget' is the last in the list.
+ Widget* getWidgetAfter(Widget* widget);
+
+ //! Returns a pointer to the Widget beforer the one referenced by 'widget' or
+ //! nullptr if 'widget' is the first in the list.
+ Widget* getWidgetBefore(Widget* widget);
+
+ //! Reports whn a new widget is shown.
+ Notifier<Widget*> currentChanged;
+
+private:
+ //! Callback for Widget::sizeChangeNotifier
+ void sizeChanged(int width, int height);
+
+private:
+ Widget* currentWidget{nullptr};
+ std::list<Widget*> widgets;
+};
+
+} // GUI::
diff --git a/dggui/tabbutton.cc b/dggui/tabbutton.cc
new file mode 100644
index 0000000..6a27f61
--- /dev/null
+++ b/dggui/tabbutton.cc
@@ -0,0 +1,132 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/***************************************************************************
+ * tabbutton.cc
+ *
+ * Thu Nov 24 18:52:26 CET 2016
+ * Copyright 2016 Bent Bisballe Nyeng
+ * deva@aasimon.org
+ ****************************************************************************/
+
+/*
+ * This file is part of DrumGizmo.
+ *
+ * DrumGizmo is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * DrumGizmo is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with DrumGizmo; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+#include "tabbutton.h"
+
+#include "painter.h"
+
+namespace GUI
+{
+
+static TabID getNextTabID()
+{
+ static TabID next{0};
+ next++;
+ return next;
+}
+
+TabButton::TabButton(Widget* parent, Widget* tab_widget)
+ : ButtonBase(parent)
+ , tab_widget(tab_widget)
+{
+ tab_id = getNextTabID();
+ CONNECT(this, clickNotifier, this, &TabButton::clickHandler);
+}
+
+TabButton::~TabButton()
+{
+}
+
+Widget* TabButton::getTabWidget()
+{
+ return tab_widget;
+}
+
+std::size_t TabButton::getMinimalWidth() const
+{
+ std::size_t padding = 15;
+ auto font_width = font.textWidth(text);
+
+ return font_width + padding;
+}
+
+std::size_t TabButton::getMinimalHeight() const
+{
+ std::size_t padding = 10;
+ auto font_height= font.textHeight(text);
+
+ return font_height + padding;
+}
+
+void TabButton::setActive(bool active)
+{
+ this->active = active;
+
+ if (active) {
+ draw_state = State::Down;
+ }
+ else {
+ draw_state = State::Up;
+ }
+
+ redraw();
+}
+
+TabID TabButton::getID() const
+{
+ return tab_id;
+}
+
+void TabButton::repaintEvent(RepaintEvent* e)
+{
+ Painter p(*this);
+
+ int padTop = 3;
+ int padLeft = 0;
+ int padTextTop = 3;
+
+ int w = width();
+ int h = height();
+ if(w == 0 || h == 0)
+ {
+ return;
+ }
+
+ if (draw_state == State::Up && !active) {
+ tab_passive.setSize(w - padLeft, h - padTop);
+ p.drawImage(padLeft, padTop, tab_passive);
+ }
+ else {
+ tab_active.setSize(w - padLeft, h - padTop);
+ p.drawImage(padLeft, padTop, tab_active);
+ }
+
+ auto x = padLeft + (width() - font.textWidth(text)) / 2;
+ auto y = padTop + padTextTop + font.textHeight(text);
+ p.drawText(x, y, font, text, true);
+}
+
+void TabButton::scrollEvent(ScrollEvent* scroll_event)
+{
+ scrollNotifier(scroll_event->delta);
+}
+
+void TabButton::clickHandler()
+{
+ switchTabNotifier(tab_widget);
+}
+
+} // GUI::
diff --git a/dggui/tabbutton.h b/dggui/tabbutton.h
new file mode 100644
index 0000000..1e9371a
--- /dev/null
+++ b/dggui/tabbutton.h
@@ -0,0 +1,85 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/***************************************************************************
+ * tabbutton.h
+ *
+ * Thu Nov 24 18:52:26 CET 2016
+ * Copyright 2016 Bent Bisballe Nyeng
+ * deva@aasimon.org
+ ****************************************************************************/
+
+/*
+ * This file is part of DrumGizmo.
+ *
+ * DrumGizmo is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * DrumGizmo is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with DrumGizmo; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+#pragma once
+
+#include <notifier.h>
+
+#include "button_base.h"
+#include "font.h"
+#include "texturedbox.h"
+
+namespace GUI
+{
+
+class ScrollEvent;
+
+using TabID = int;
+
+class TabButton
+ : public ButtonBase
+{
+public:
+ TabButton(Widget* parent, Widget* tab_widget);
+ virtual ~TabButton();
+
+ Widget* getTabWidget();
+ std::size_t getMinimalWidth() const;
+ std::size_t getMinimalHeight() const;
+ void setActive(bool active);
+
+ TabID getID() const;
+
+ Notifier<Widget*> switchTabNotifier;
+ Notifier<float> scrollNotifier; // float delta
+
+protected:
+ // From Widget:
+ virtual void repaintEvent(RepaintEvent* e) override;
+ virtual void scrollEvent(ScrollEvent* scroll_event) override;
+
+private:
+ TabID tab_id;
+
+ void clickHandler();
+
+ Widget* tab_widget;
+ bool active{false};
+
+ TexturedBox tab_active{getImageCache(), ":resources/tab.png",
+ 0, 0, // atlas offset (x, y)
+ 5, 1, 5, // dx1, dx2, dx3
+ 5, 13, 1}; // dy1, dy2, dy3
+
+ TexturedBox tab_passive{getImageCache(), ":resources/tab.png",
+ 11, 0, // atlas offset (x, y)
+ 5, 1, 5, // dx1, dx2, dx3
+ 5, 13, 1}; // dy1, dy2, dy3
+
+ Font font{":resources/fontemboss.png"};
+};
+
+} // GUI::
diff --git a/dggui/tabwidget.cc b/dggui/tabwidget.cc
new file mode 100644
index 0000000..635f1bd
--- /dev/null
+++ b/dggui/tabwidget.cc
@@ -0,0 +1,214 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/***************************************************************************
+ * tabwidget.cc
+ *
+ * Thu Nov 24 17:46:22 CET 2016
+ * Copyright 2016 Bent Bisballe Nyeng
+ * deva@aasimon.org
+ ****************************************************************************/
+
+/*
+ * This file is part of DrumGizmo.
+ *
+ * DrumGizmo is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * DrumGizmo is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with DrumGizmo; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+#include "tabwidget.h"
+
+#include "painter.h"
+
+namespace GUI
+{
+
+TabWidget::TabWidget(Widget *parent)
+ : Widget(parent)
+ , stack(this)
+{
+ CONNECT(this, sizeChangeNotifier, this, &TabWidget::sizeChanged);
+ CONNECT(&stack, currentChanged, this, &TabWidget::setActiveButtons);
+}
+
+TabID TabWidget::addTab(const std::string& title, Widget* widget)
+{
+ buttons.emplace_back(this, widget);
+ auto& button = buttons.back();
+ button.setText(title);
+ stack.addWidget(widget);
+ CONNECT(&button, switchTabNotifier, this, &TabWidget::switchTab);
+ CONNECT(&button, scrollNotifier, this, &TabWidget::rotateTab);
+ sizeChanged(width(), height());
+ return button.getID();
+}
+
+void TabWidget::setTabWidth(std::size_t width)
+{
+ tab_width = width;
+ relayout();
+}
+
+std::size_t TabWidget::getTabWidth() const
+{
+ return tab_width;
+}
+
+void TabWidget::setVisible(TabID tab_id, bool visible)
+{
+ for (auto& button : buttons)
+ {
+ if(button.getID() == tab_id)
+ {
+ button.setVisible(visible);
+ relayout();
+ return;
+ }
+ }
+}
+
+std::size_t TabWidget::getBarHeight() const
+{
+ return topbar.height();
+}
+
+void TabWidget::rotateTab(float delta)
+{
+ Widget* widget{nullptr};
+ Widget* current = stack.getCurrentWidget();
+ if(delta > 0.0f)
+ {
+ while((widget = stack.getWidgetAfter(current)) != nullptr)
+ {
+ auto button = getButtonFromWidget(widget);
+ if(!button || !button->visible())
+ {
+ current = widget;
+ continue;
+ }
+ break;
+ }
+ }
+ else
+ {
+ while((widget = stack.getWidgetBefore(current)) != nullptr)
+ {
+ auto button = getButtonFromWidget(widget);
+ if(!button || !button->visible())
+ {
+ current = widget;
+ continue;
+ }
+ break;
+ }
+ }
+
+ if(widget)
+ {
+ switchTab(widget);
+ }
+}
+
+void TabWidget::switchTab(Widget* tab_widget)
+{
+ stack.setCurrentWidget(tab_widget);
+}
+
+void TabWidget::setActiveButtons(Widget* current_widget)
+{
+ for (auto& button : buttons) {
+ if (button.getTabWidget() == current_widget) {
+ button.setActive(true);
+ }
+ else
+ {
+ button.setActive(false);
+ }
+ }
+}
+
+void TabWidget::sizeChanged(int width, int height)
+{
+ std::size_t pos = 0;
+
+ int button_width = tab_width;
+ int bar_height = 25;
+ int button_border_width = 10;
+
+ int button_padding_left = 25;
+ int button_padding_inner = 3;
+ int logo_padding_right = button_padding_left / 2;
+
+ Painter p(*this);
+
+ if(buttons.size() > 0)
+ {
+ for(auto& button : buttons)
+ {
+ if(!button.visible())
+ {
+ continue;
+ }
+ int min_width = button.getMinimalWidth();
+ button_width = std::max(button_width, min_width + button_border_width);
+ }
+
+ button_width = std::min(button_width, width / (int)buttons.size());
+ }
+
+ // draw the upper bar
+ topbar.setSize(width, bar_height);
+ p.drawImage(0, 0, topbar);
+ auto x_logo = width - toplogo.width() - logo_padding_right;
+ auto y_logo = (bar_height - toplogo.height()) / 2;
+ p.drawImage(x_logo, y_logo, toplogo);
+
+ // place the buttons
+ pos = button_padding_left;
+ for(auto& button : buttons)
+ {
+ if(!button.visible())
+ {
+ continue;
+ }
+ button.resize(button_width, bar_height);
+ button.move(pos, 0);
+ pos += button_width + button_padding_inner;
+ }
+
+ stack.move(0, bar_height);
+ stack.resize(width, std::max((int)height - bar_height, 0));
+}
+
+void TabWidget::relayout()
+{
+ sizeChanged(TabWidget::width(), TabWidget::height()); // Force re-layout
+}
+
+const TabButton* TabWidget::getButtonFromWidget(const Widget* tab_widget)
+{
+ if(tab_widget == nullptr)
+ {
+ return nullptr;
+ }
+
+ for(auto& button : buttons)
+ {
+ if(button.getTabWidget() == tab_widget)
+ {
+ return &button;
+ }
+ }
+
+ return nullptr;
+}
+
+} // GUI::
diff --git a/dggui/tabwidget.h b/dggui/tabwidget.h
new file mode 100644
index 0000000..129826a
--- /dev/null
+++ b/dggui/tabwidget.h
@@ -0,0 +1,84 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/***************************************************************************
+ * tabwidget.h
+ *
+ * Thu Nov 24 17:46:22 CET 2016
+ * Copyright 2016 Bent Bisballe Nyeng
+ * deva@aasimon.org
+ ****************************************************************************/
+
+/*
+ * This file is part of DrumGizmo.
+ *
+ * DrumGizmo is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * DrumGizmo is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with DrumGizmo; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+#pragma once
+
+#include "widget.h"
+#include "tabbutton.h"
+#include "stackedwidget.h"
+#include "texture.h"
+
+namespace GUI
+{
+
+class TabWidget
+ : public Widget
+{
+public:
+ TabWidget(Widget *parent);
+
+ //! Add new tab to the tab widget.
+ //! \param title The title to display on the tab button.
+ //! \param widget The widget to show in the tab.
+ //! \returns The TabID of the newly added tab.
+ TabID addTab(const std::string& title, Widget* widget);
+
+ std::size_t getBarHeight() const;
+
+ void setTabWidth(std::size_t width);
+ std::size_t getTabWidth() const;
+
+ void setVisible(TabID tab_id, bool visible);
+
+private:
+ //! Callback for Widget::sizeChangeNotifier
+ void sizeChanged(int width, int height);
+
+private:
+ void relayout();
+ //! Switch to the next tab if delta is > 0 or previous tab if delta is <= 0.
+ void rotateTab(float delta);
+ void switchTab(Widget* tabWidget);
+ void setActiveButtons(Widget* current_widget);
+
+ const TabButton* getButtonFromWidget(const Widget* tab_widget);
+
+ std::list<TabButton> buttons;
+ StackedWidget stack;
+
+ TexturedBox topbar{getImageCache(), ":resources/topbar.png",
+ 0, 0, // atlas offset (x, y)
+ 1, 1, 1, // dx1, dx2, dx3
+ 17, 1, 1}; // dy1, dy2, dy3
+
+ Texture toplogo{getImageCache(), ":resources/toplogo.png",
+ 0, 0, // atlas offset (x, y)
+ 95, 17}; // width, height
+
+ std::size_t tab_width{64};
+};
+
+} // GUI::
diff --git a/dggui/textedit.cc b/dggui/textedit.cc
new file mode 100644
index 0000000..9688b82
--- /dev/null
+++ b/dggui/textedit.cc
@@ -0,0 +1,201 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/***************************************************************************
+ * lineedit.cc
+ *
+ * Tue Oct 21 11:25:26 CEST 2014
+ * Copyright 2014 Jonas Suhr Christensen
+ * jsc@umbraculum.org
+ ****************************************************************************/
+
+/*
+ * This file is part of DrumGizmo.
+ *
+ * DrumGizmo is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * DrumGizmo is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with DrumGizmo; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+#include "textedit.h"
+
+#include "painter.h"
+
+namespace GUI
+{
+
+TextEdit::TextEdit(Widget* parent) : Widget(parent), scroll(this)
+{
+ setReadOnly(true);
+
+ scroll.move(width() - 2*x_border - 3, y_border - 1);
+ scroll.resize(16, 100);
+ CONNECT(&scroll, valueChangeNotifier, this, &TextEdit::scrolled);
+}
+
+TextEdit::~TextEdit()
+{
+}
+
+void TextEdit::resize(std::size_t width, std::size_t height)
+{
+ Widget::resize(width, height);
+
+ needs_preprocessing = true;
+ scroll.move(width - 2*x_border - 3, y_border - 1);
+ scroll.resize(scroll.width(), std::max((int)height - 2*(y_border - 1), 0));
+}
+
+void TextEdit::setReadOnly(bool readonly)
+{
+ this->readonly = readonly;
+}
+
+bool TextEdit::readOnly()
+{
+ return readonly;
+}
+
+void TextEdit::setText(const std::string& text)
+{
+ this->text = text;
+
+ needs_preprocessing = true;
+ redraw();
+ textChangedNotifier();
+}
+
+std::string TextEdit::getText()
+{
+ return text;
+}
+
+void TextEdit::preprocessText()
+{
+ std::vector<std::string> lines;
+
+ preprocessed_text.clear();
+ std::string text = this->text;
+
+ // Handle tab characters
+ for(std::size_t i = 0; i < text.length(); ++i)
+ {
+ char ch = text.at(i);
+ if(ch == '\t')
+ {
+ text.erase(i, 1);
+ text.insert(i, 4, ' ');
+ }
+ }
+
+ // Handle "\r"
+ for(std::size_t i = 0; i < text.length(); ++i)
+ {
+ char ch = text.at(i);
+ if(ch == '\r')
+ {
+ text.erase(i, 1);
+ }
+ }
+
+ // Handle new line characters
+ std::size_t pos = 0;
+ do
+ {
+ pos = text.find("\n");
+ lines.push_back(text.substr(0, pos));
+ text = text.substr(pos + 1);
+ } while(pos != std::string::npos);
+
+ // Wrap long lines
+ auto const max_width = width() - 2*x_border - 10 - scroll.width();
+ for(auto const& line: lines)
+ {
+ std::string valid;
+ std::string current;
+ for(auto c: line)
+ {
+ current += c;
+ if(c == ' ')
+ {
+ valid.append(current.substr(valid.size()));
+ }
+
+ if(font.textWidth(current) >= max_width)
+ {
+ if(valid.empty())
+ {
+ current.pop_back();
+ preprocessed_text.push_back(current);
+ current = c;
+ }
+ else
+ {
+ current = current.substr(valid.size());
+ valid.pop_back();
+ preprocessed_text.push_back(valid);
+ valid.clear();
+ }
+ }
+ }
+ preprocessed_text.push_back(current);
+ }
+}
+
+void TextEdit::repaintEvent(RepaintEvent* repaintEvent)
+{
+ if(needs_preprocessing)
+ {
+ preprocessText();
+ }
+
+ Painter p(*this);
+
+ // update values of scroll bar
+ scroll.setRange(height() / font.textHeight());
+ scroll.setMaximum(preprocessed_text.size());
+
+ if((width() == 0) || (height() == 0))
+ {
+ return;
+ }
+
+ box.setSize(width(), height());
+ p.drawImage(0, 0, box);
+ p.setColour(Colour(183.0f/255.0f, 219.0f/255.0f, 255.0f/255.0f, 1.0f));
+
+ int ypos = font.textHeight() + y_border;
+
+ auto scroll_value = scroll.value();
+ for(std::size_t i = 0; i < preprocessed_text.size() - scroll_value; ++i)
+ {
+ if(i * font.textHeight() >= (height() - y_border - font.textHeight()))
+ {
+ break;
+ }
+
+ auto const& line = preprocessed_text[scroll_value + i];
+ p.drawText(x_border, ypos, font, line);
+ ypos += font.textHeight();
+ }
+}
+
+void TextEdit::scrollEvent(ScrollEvent* scrollEvent)
+{
+ scroll.setValue(scroll.value() + scrollEvent->delta);
+}
+
+void TextEdit::scrolled(int value)
+{
+ (void)value;
+ redraw();
+}
+
+} // GUI::
diff --git a/dggui/textedit.h b/dggui/textedit.h
new file mode 100644
index 0000000..17a04ff
--- /dev/null
+++ b/dggui/textedit.h
@@ -0,0 +1,92 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/***************************************************************************
+ * textedit.h
+ *
+ * Tue Oct 21 11:23:58 CEST 2014
+ * Copyright 2014 Jonas Suhr Christensen
+ * jsc@umbraculum.org
+ ****************************************************************************/
+
+/*
+ * This file is part of DrumGizmo.
+ *
+ * DrumGizmo is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * DrumGizmo is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with DrumGizmo; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+#pragma once
+
+#include <string>
+#include <vector>
+
+#include "font.h"
+#include "notifier.h"
+#include "scrollbar.h"
+#include "texturedbox.h"
+#include "widget.h"
+
+namespace GUI
+{
+
+class TextEdit
+ : public Widget
+{
+public:
+ TextEdit(Widget* parent);
+ virtual ~TextEdit();
+
+ // From Widget
+ bool isFocusable() override
+ {
+ return true;
+ }
+ void resize(std::size_t width, std::size_t height) override;
+
+ std::string getText();
+ void setText(const std::string& text);
+
+ void setReadOnly(bool readonly);
+ bool readOnly();
+
+ void preprocessText();
+
+ Notifier<> textChangedNotifier;
+
+protected:
+ // From Widget
+ virtual void repaintEvent(RepaintEvent* repaintEvent) override;
+ void scrollEvent(ScrollEvent* scrollEvent) override;
+
+private:
+ void scrolled(int value);
+
+ TexturedBox box{getImageCache(), ":resources/widget.png", 0,
+ 0, // atlas offset (x, y)
+ 7, 1, 7, // dx1, dx2, dx3
+ 7, 63, 7}; // dy1, dy2, dy3
+
+ static constexpr int x_border{10};
+ static constexpr int y_border{8};
+
+ ScrollBar scroll;
+ Font font;
+
+ std::string text;
+
+ bool readonly{true};
+ bool needs_preprocessing{false};
+
+ std::vector<std::string> preprocessed_text;
+};
+
+} // GUI::
diff --git a/dggui/texture.cc b/dggui/texture.cc
new file mode 100644
index 0000000..8cd7040
--- /dev/null
+++ b/dggui/texture.cc
@@ -0,0 +1,72 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/***************************************************************************
+ * texture.cc
+ *
+ * Sat Jun 4 21:18:11 CEST 2016
+ * Copyright 2016 Bent Bisballe Nyeng
+ * deva@aasimon.org
+ ****************************************************************************/
+
+/*
+ * This file is part of DrumGizmo.
+ *
+ * DrumGizmo is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * DrumGizmo is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with DrumGizmo; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+#include "texture.h"
+
+namespace GUI
+{
+
+Texture::Texture(ImageCache& image_cache, const std::string& filename,
+ std::size_t x, std::size_t y,
+ std::size_t width, std::size_t height)
+ : ScopedImageBorrower(image_cache, filename)
+ , _x(x)
+ , _y(y)
+ , _width(width>image.width()?image.width():width)
+ , _height(height>image.height()?image.height():height)
+{
+}
+
+size_t Texture::width() const
+{
+ return _width;
+}
+
+size_t Texture::height() const
+{
+ return _height;
+}
+
+const Colour& Texture::getPixel(size_t x, size_t y) const
+{
+ if(x > _width || y > _height)
+ {
+ return outOfRange;
+ }
+ return image.getPixel(x + _x, y + _y);
+}
+
+const std::uint8_t* Texture::line(std::size_t y, std::size_t x_offset) const
+{
+ return image.line(y + _y) + _x * 4 + x_offset * 4;
+}
+
+bool Texture::hasAlpha() const
+{
+ return true;
+}
+
+} // GUI::
diff --git a/dggui/texture.h b/dggui/texture.h
new file mode 100644
index 0000000..c751ed4
--- /dev/null
+++ b/dggui/texture.h
@@ -0,0 +1,64 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/***************************************************************************
+ * texture.h
+ *
+ * Sat Jun 4 21:18:11 CEST 2016
+ * Copyright 2016 Bent Bisballe Nyeng
+ * deva@aasimon.org
+ ****************************************************************************/
+
+/*
+ * This file is part of DrumGizmo.
+ *
+ * DrumGizmo is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * DrumGizmo is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with DrumGizmo; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+#pragma once
+
+#include <string>
+#include <limits>
+
+#include "imagecache.h"
+#include "image.h"
+
+namespace GUI
+{
+
+class Texture
+ : public ScopedImageBorrower
+ , public Drawable
+{
+public:
+ Texture(ImageCache& image_cache, const std::string& filename,
+ std::size_t x = 0, std::size_t y = 0,
+ std::size_t width = std::numeric_limits<std::size_t>::max(),
+ std::size_t height = std::numeric_limits<std::size_t>::max());
+
+ size_t width() const override;
+ size_t height() const override;
+
+ const Colour& getPixel(size_t x, size_t y) const override;
+ const std::uint8_t* line(std::size_t y,
+ std::size_t x_offset = 0) const override;
+ bool hasAlpha() const override;
+
+private:
+ std::size_t _x;
+ std::size_t _y;
+ std::size_t _width;
+ std::size_t _height;
+ Colour outOfRange{0.0f, 0.0f, 0.0f, 0.0f};
+};
+
+} // GUI::
diff --git a/dggui/texturedbox.cc b/dggui/texturedbox.cc
new file mode 100644
index 0000000..e48353a
--- /dev/null
+++ b/dggui/texturedbox.cc
@@ -0,0 +1,147 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/***************************************************************************
+ * texturedbox.cc
+ *
+ * Sun Jun 5 12:22:15 CEST 2016
+ * Copyright 2016 Bent Bisballe Nyeng
+ * deva@aasimon.org
+ ****************************************************************************/
+
+/*
+ * This file is part of DrumGizmo.
+ *
+ * DrumGizmo is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * DrumGizmo is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with DrumGizmo; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+#include "texturedbox.h"
+
+#include <cassert>
+
+namespace GUI
+{
+
+TexturedBox::TexturedBox(ImageCache& image_cache, const std::string& filename,
+ std::size_t x0, std::size_t y0,
+ std::size_t dx1, std::size_t dx2, std::size_t dx3,
+ std::size_t dy1, std::size_t dy2, std::size_t dy3)
+ : seg_a(image_cache, filename, x0 , y0 , dx1, dy1)
+ , seg_b(image_cache, filename, x0 + dx1 , y0 , dx2, dy1)
+ , seg_c(image_cache, filename, x0 + dx1 + dx2, y0 , dx3, dy1)
+ , seg_d(image_cache, filename, x0 , y0 + dy1 , dx1, dy2)
+ , seg_e(image_cache, filename, x0 + dx1 , y0 + dy1 , dx2, dy2)
+ , seg_f(image_cache, filename, x0 + dx1 + dx2, y0 + dy1 , dx3, dy2)
+ , seg_g(image_cache, filename, x0 , y0 + dy1 + dy2, dx1, dy3)
+ , seg_h(image_cache, filename, x0 + dx1 , y0 + dy1 + dy2, dx2, dy3)
+ , seg_i(image_cache, filename, x0 + dx1 + dx2, y0 + dy1 + dy2, dx3, dy3)
+ , dx1(dx1)
+ , dx2(dx2)
+ , dx3(dx3)
+ , dy1(dy1)
+ , dy2(dy2)
+ , dy3(dy3)
+ , _width(dx1 + dx2 + dx3)
+ , _height(dy1 + dy2 + dy3)
+{
+}
+
+std::size_t TexturedBox::width() const
+{
+ return _width;
+}
+
+std::size_t TexturedBox::height() const
+{
+ return _height;
+}
+
+void TexturedBox::setSize(std::size_t width, std::size_t height)
+{
+ _width = width;
+ _height = height;
+}
+
+const Colour& TexturedBox::getPixel(std::size_t x, std::size_t y) const
+{
+ assert(x < _width);
+ assert(y < _height);
+
+ if(y < dy1) // row 1
+ {
+ if(x < dx1) // col 1
+ {
+ return seg_a.getPixel(x, y);
+ }
+ else if(x < (_width - dx3)) // col 2
+ {
+ float scale = (float)(x - dx1) / (float)(_width - dx1 - dx3);
+ assert(seg_b.width() == dx2);
+ return seg_b.getPixel(scale * dx2, y);
+ }
+ else // col 3
+ {
+ return seg_c.getPixel(x - (_width - dx3), y);
+ }
+ }
+ else if(y < (_height - dy3)) // row 2
+ {
+ if(x < dx1) // col 1
+ {
+ // TODO: Apply vertical scale
+ float scale = (float)(y - dy1) / (float)(_height - dy1 - dy3);
+ return seg_d.getPixel(x, scale * dy2);
+ }
+ else if(x < (_width - dx3)) // col 2
+ {
+ float scale_x = (float)(x - dx1) / (float)(_width - dx1 - dx3);
+ float scale_y = (float)(y - dy1) / (float)(_height - dy1 - dy3);
+ return seg_e.getPixel(scale_x * dx2, scale_y * dy2);
+ }
+ else // col 3
+ {
+ float scale = (float)(y - dy1) / (float)(_height - dy1 - dy3);
+ return seg_f.getPixel(x - (_width - dx3), scale * dy2);
+ }
+ }
+ else // row 3
+ {
+ if(x < dx1) // col 1
+ {
+ return seg_g.getPixel(x, y - (_height - dy3));
+ }
+ else if(x < (_width - dx3)) // col 2
+ {
+ float scale = (float)(x - dx1) / (float)(_width - dx1 - dx3);
+ return seg_h.getPixel(scale * dx2, y - (_height - dy3));
+ }
+ else // col 3
+ {
+ return seg_i.getPixel(x - (_width - dx3), y - (_height - dy3));
+ }
+ }
+
+ return outOfRange;
+}
+
+const std::uint8_t* TexturedBox::line(std::size_t y, std::size_t x_offset) const
+{
+ // TODO: Gather line into temporary buffer?
+ return nullptr;
+}
+
+bool TexturedBox::hasAlpha() const
+{
+ return true;
+}
+
+} // GUI::
diff --git a/dggui/texturedbox.h b/dggui/texturedbox.h
new file mode 100644
index 0000000..7aa3967
--- /dev/null
+++ b/dggui/texturedbox.h
@@ -0,0 +1,119 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/***************************************************************************
+ * texturedbox.h
+ *
+ * Sun Jun 5 12:22:14 CEST 2016
+ * Copyright 2016 Bent Bisballe Nyeng
+ * deva@aasimon.org
+ ****************************************************************************/
+
+/*
+ * This file is part of DrumGizmo.
+ *
+ * DrumGizmo is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * DrumGizmo is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with DrumGizmo; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+#pragma once
+
+#include "drawable.h"
+#include "imagecache.h"
+#include "texture.h"
+
+namespace GUI
+{
+
+class TexturedBox
+ : public Drawable
+{
+public:
+ //! Draw a box from 9 image segments nested inside the same image.
+ //! An image says more than a thousand words:
+ //! .----------------------------------------.
+ //! | (x0, y0) |
+ //! | \ dx1 dx2 dx3 |
+ //! | .-----+-----------+-----. \ |
+ //! | dy1 | A | <--B--> | C | | |
+ //! | +-----+-----------+-----+ | |
+ //! | | /|\ | /|\ | /|\ | | h |
+ //! | | | | | | | | | e |
+ //! | dy2 | D | <--E--> | F | > i |
+ //! | | | | | | | | | g |
+ //! | | \|/ | \|/ | \|/ | | h |
+ //! | +-----+-----------+-----+ | t |
+ //! | dy3 | G | <--H--> | I | | |
+ //! | `-----+-----------+-----` / |
+ //! | |
+ //! | \___________ ___________/ |
+ //! | V |
+ //! | width |
+ //! `----------------------------------------`
+ //!
+ //! \param image_cache A reference to the image cache object.
+ //! \param filename The filename of the texture image to use.
+ //! \param (x0, y0) is coordinate of the upper left corner of the A segment.
+ //! \param (width, height) is the total rendered size of the Box.
+ //! \param dx1 is the width of the A, C and F segments.
+ //! \param dx2 is the width of the B, E and H segments.
+ //! \param dx3 is the width of the C, F and I segments.
+ //! \param dy1 is the height of the A, B and C segments.
+ //! \param dy2 is the height of the D, E and F segments.
+ //! \param dy3 is the height of the G, G and I segments.
+ //!
+ //! Segments A, C, G and I are drawn with no stretch.
+ //! Segments B and H are stretched horizontally to fill the
+ //! gaps between A, C and G, I so that resulting width is 'width'
+ //! Segments D and F are stretched vertically to fill the
+ //! gaps between A, G and C, I so that resulting height is 'height'
+ //! Segment E will be stretched both horizontally and vertically
+ //! to fill the inner space between B, H and D, F.
+ TexturedBox(ImageCache& image_cache, const std::string& filename,
+ std::size_t x0, std::size_t y0,
+ std::size_t dx1, std::size_t dx2, std::size_t dx3,
+ std::size_t dy1, std::size_t dy2, std::size_t dy3);
+
+ // From Drawable:
+ std::size_t width() const override;
+ std::size_t height() const override;
+
+ void setSize(std::size_t width, std::size_t height);
+
+ const Colour& getPixel(std::size_t x, std::size_t y) const override;
+ const std::uint8_t* line(std::size_t y,
+ std::size_t x_offset = 0) const override;
+ bool hasAlpha() const override;
+
+private:
+ Texture seg_a;
+ Texture seg_b;
+ Texture seg_c;
+ Texture seg_d;
+ Texture seg_e;
+ Texture seg_f;
+ Texture seg_g;
+ Texture seg_h;
+ Texture seg_i;
+
+ std::size_t dx1;
+ std::size_t dx2;
+ std::size_t dx3;
+ std::size_t dy1;
+ std::size_t dy2;
+ std::size_t dy3;
+ std::size_t _width{100};
+ std::size_t _height{100};
+
+ Colour outOfRange{0.0f, 0.0f, 0.0f, 0.0f};
+};
+
+} // GUI::
diff --git a/dggui/toggle.cc b/dggui/toggle.cc
new file mode 100644
index 0000000..39587de
--- /dev/null
+++ b/dggui/toggle.cc
@@ -0,0 +1,110 @@
+/* -*- Mode: c++ -*- */
+/***************************************************************************
+ * toggle.cc
+ *
+ * Wed Mar 22 22:58:57 CET 2017
+ * Copyright 2017 André Nusser
+ * andre.nusser@googlemail.com
+ ****************************************************************************/
+
+/*
+ * This file is part of DrumGizmo.
+ *
+ * DrumGizmo is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * DrumGizmo is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with DrumGizmo; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+#include "toggle.h"
+
+namespace GUI
+{
+
+Toggle::Toggle(Widget* parent) : Widget(parent)
+{
+}
+
+void Toggle::buttonEvent(ButtonEvent* buttonEvent)
+{
+ // Ignore everything except left clicks.
+ if(buttonEvent->button != MouseButton::left)
+ {
+ return;
+ }
+
+ if((buttonEvent->direction == Direction::up) || buttonEvent->doubleClick)
+ {
+ buttonDown = false;
+ clicked = false;
+ if(inCheckbox)
+ {
+ internalSetChecked(!state);
+ }
+ }
+ else
+ {
+ buttonDown = true;
+ clicked = true;
+ }
+
+ redraw();
+}
+
+void Toggle::setText(std::string text)
+{
+ _text = text;
+ redraw();
+}
+
+bool Toggle::checked()
+{
+ return state;
+}
+
+void Toggle::setChecked(bool c)
+{
+ internalSetChecked(c);
+}
+
+void Toggle::mouseLeaveEvent()
+{
+ inCheckbox = false;
+ if(buttonDown)
+ {
+ clicked = false;
+ redraw();
+ }
+}
+
+void Toggle::mouseEnterEvent()
+{
+ inCheckbox = true;
+ if(buttonDown)
+ {
+ clicked = true;
+ redraw();
+ }
+}
+
+void Toggle::internalSetChecked(bool checked)
+{
+ if(state == checked)
+ {
+ return;
+ }
+
+ state = checked;
+ stateChangedNotifier(state);
+ redraw();
+}
+
+} // GUI::
diff --git a/dggui/toggle.h b/dggui/toggle.h
new file mode 100644
index 0000000..3466459
--- /dev/null
+++ b/dggui/toggle.h
@@ -0,0 +1,70 @@
+/* -*- Mode: c++ -*- */
+/***************************************************************************
+ * toggle.h
+ *
+ * Wed Mar 22 22:58:57 CET 2017
+ * Copyright 2017 André Nusser
+ * andre.nusser@googlemail.com
+ ****************************************************************************/
+
+/*
+ * This file is part of DrumGizmo.
+ *
+ * DrumGizmo is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * DrumGizmo is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with DrumGizmo; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+#pragma once
+
+#include "widget.h"
+
+#include <notifier.h>
+
+namespace GUI
+{
+
+class Toggle : public Widget
+{
+public:
+ Toggle(Widget* parent);
+ virtual ~Toggle() = default;
+
+ void setText(std::string text);
+
+ // From Widget:
+ bool isFocusable() override { return true; }
+ bool catchMouse() override { return true; }
+
+ bool checked();
+ void setChecked(bool checked);
+
+ Notifier<bool> stateChangedNotifier;
+
+protected:
+ // From Widget:
+ virtual void buttonEvent(ButtonEvent* buttonEvent) override;
+ virtual void mouseLeaveEvent() override;
+ virtual void mouseEnterEvent() override;
+
+ bool state{false};
+ bool clicked{false};
+ bool buttonDown{false};
+ bool inCheckbox{false};
+
+ std::string _text;
+
+private:
+ void internalSetChecked(bool checked);
+};
+
+} // GUI::
diff --git a/dggui/tooltip.cc b/dggui/tooltip.cc
new file mode 100644
index 0000000..e251ed5
--- /dev/null
+++ b/dggui/tooltip.cc
@@ -0,0 +1,200 @@
+/* -*- Mode: c++ -*- */
+/***************************************************************************
+ * tooltip.cc
+ *
+ * Wed May 8 17:31:42 CEST 2019
+ * Copyright 2019 Bent Bisballe Nyeng
+ * deva@aasimon.org
+ ****************************************************************************/
+
+/*
+ * This file is part of DrumGizmo.
+ *
+ * DrumGizmo is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * DrumGizmo is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with DrumGizmo; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+#include "tooltip.h"
+
+#include "painter.h"
+#include "font.h"
+#include "window.h"
+#include <iostream>
+
+namespace GUI
+{
+
+Tooltip::Tooltip(Widget *parent)
+ : Widget(parent->window())
+ , activating_widget(parent)
+{
+ resize(32, 32);
+}
+
+Tooltip::~Tooltip()
+{
+}
+
+void Tooltip::setText(const std::string& text)
+{
+ this->text = text;
+ needs_preprocessing = true;
+ redraw();
+}
+
+void Tooltip::resize(std::size_t width, std::size_t height)
+{
+ Widget::resize(width, height);
+}
+
+void Tooltip::repaintEvent(RepaintEvent* repaintEvent)
+{
+ if(needs_preprocessing)
+ {
+ preprocessText();
+ }
+
+ Painter p(*this);
+
+ if((width() == 0) || (height() == 0))
+ {
+ return;
+ }
+
+ box.setSize(width(), height());
+ p.drawImage(0, 0, box);
+ p.setColour(Colour(183.0f/255.0f, 219.0f/255.0f, 255.0f/255.0f, 1.0f));
+
+ int ypos = font.textHeight() + y_border;
+
+ for(std::size_t i = 0; i < preprocessed_text.size(); ++i)
+ {
+ if(i * font.textHeight() >= (height() - y_border - font.textHeight()))
+ {
+ break;
+ }
+
+ auto const& line = preprocessed_text[i];
+ p.drawText(x_border, ypos, font, line);
+ ypos += font.textHeight();
+ }
+}
+
+void Tooltip::preprocessText()
+{
+ std::vector<std::string> lines;
+
+ preprocessed_text.clear();
+ std::string text = this->text;
+
+ // Handle tab characters
+ for(std::size_t i = 0; i < text.length(); ++i)
+ {
+ char ch = text.at(i);
+ if(ch == '\t')
+ {
+ text.erase(i, 1);
+ text.insert(i, 4, ' ');
+ }
+ }
+
+ // Handle "\r"
+ for(std::size_t i = 0; i < text.length(); ++i)
+ {
+ char ch = text.at(i);
+ if(ch == '\r')
+ {
+ text.erase(i, 1);
+ }
+ }
+
+ // Handle new line characters
+ std::size_t pos = 0;
+ do
+ {
+ pos = text.find("\n");
+ lines.push_back(text.substr(0, pos));
+ text = text.substr(pos + 1);
+ } while(pos != std::string::npos);
+
+ max_text_width = 0;
+ total_text_height = 0;
+ for(auto const& line: lines)
+ {
+ std::string valid;
+ std::string current;
+ for(auto c: line)
+ {
+ current += c;
+ if(c == ' ')
+ {
+ valid.append(current.substr(valid.size()));
+ }
+ }
+ preprocessed_text.push_back(current);
+
+ max_text_width = std::max(max_text_width, font.textWidth(line));
+ total_text_height += font.textHeight(line);
+ }
+}
+
+void Tooltip::mouseLeaveEvent()
+{
+ hide();
+}
+
+void Tooltip::show()
+{
+ if(needs_preprocessing)
+ {
+ preprocessText();
+ }
+
+ resize(max_text_width + 2*x_border, total_text_height + 2*y_border);
+
+ int x = activating_widget->translateToWindowX();
+ int y = activating_widget->translateToWindowY();
+
+ if(x + width() > window()->width())
+ {
+ x -= width();
+ x += activating_widget->width();
+ }
+
+ if(y + height() > window()->height())
+ {
+ y -= height();
+ y += activating_widget->height();
+ }
+
+ // Make sure the tip is displayed inside the window
+ x = std::max(x, 0);
+ y = std::max(y, 0);
+
+ move(x, y);
+ Widget::show();
+
+ // TODO: This should be handled differently
+ // Hack to notify the window that the mouse is now inside the tooltip.
+ window()->setMouseFocus(this);
+}
+
+void Tooltip::buttonEvent(ButtonEvent* buttonEvent)
+{
+ if(buttonEvent->direction == Direction::down)
+ {
+ hide();
+ }
+}
+
+} // GUI::
diff --git a/dggui/tooltip.h b/dggui/tooltip.h
new file mode 100644
index 0000000..9d1619d
--- /dev/null
+++ b/dggui/tooltip.h
@@ -0,0 +1,78 @@
+/* -*- Mode: c++ -*- */
+/***************************************************************************
+ * tooltip.h
+ *
+ * Wed May 8 17:31:42 CEST 2019
+ * Copyright 2019 Bent Bisballe Nyeng
+ * deva@aasimon.org
+ ****************************************************************************/
+
+/*
+ * This file is part of DrumGizmo.
+ *
+ * DrumGizmo is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * DrumGizmo is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with DrumGizmo; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+#pragma once
+
+#include <string.h>
+#include <vector>
+
+#include <notifier.h>
+
+#include "widget.h"
+#include "painter.h"
+#include "texturedbox.h"
+#include "font.h"
+
+namespace GUI
+{
+
+class Tooltip
+ : public Widget
+{
+public:
+ Tooltip(Widget *parent);
+ virtual ~Tooltip();
+
+ void setText(const std::string& text);
+
+ // From Widget:
+ virtual void repaintEvent(RepaintEvent* repaint_event) override;
+ virtual void resize(std::size_t height, std::size_t width) override;
+ virtual void mouseLeaveEvent() override;
+ virtual void show() override;
+ virtual void buttonEvent(ButtonEvent* buttonEvent) override;
+
+private:
+ void preprocessText();
+
+ TexturedBox box{getImageCache(), ":resources/thinlistbox.png",
+ 0, 0, // atlas offset (x, y)
+ 1, 1, 1, // dx1, dx2, dx3
+ 1, 1, 1}; // dy1, dy2, dy3
+ Font font;
+
+ static constexpr int x_border{10};
+ static constexpr int y_border{8};
+
+ bool needs_preprocessing{false};
+ std::string text;
+ std::vector<std::string> preprocessed_text;
+ std::size_t max_text_width{0};
+ std::size_t total_text_height{0};
+ Widget* activating_widget;
+};
+
+} // GUI::
diff --git a/dggui/uitranslation.cc b/dggui/uitranslation.cc
new file mode 100644
index 0000000..0546be8
--- /dev/null
+++ b/dggui/uitranslation.cc
@@ -0,0 +1,51 @@
+/* -*- Mode: c++ -*- */
+/***************************************************************************
+ * uitranslation.cc
+ *
+ * Thu May 7 18:04:40 CEST 2020
+ * Copyright 2020 Bent Bisballe Nyeng
+ * deva@aasimon.org
+ ****************************************************************************/
+
+/*
+ * This file is part of DrumGizmo.
+ *
+ * DrumGizmo is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * DrumGizmo is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with DrumGizmo; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+#include "uitranslation.h"
+
+#ifdef WITH_NLS
+
+#include "resource.h"
+
+UITranslation::UITranslation()
+{
+ auto lang = Translation::getISO639LanguageName();
+ printf("LANG: %s\n", lang.data());
+ std::string res = ":locale/";
+ res += lang + ".mo";
+
+ GUI::Resource mo(res);
+ if(!mo.valid())
+ {
+ printf("Locale not in resources - use default\n");
+ // Locale not in resources - use default
+ return;
+ }
+ printf("Using mo: %s\n", res.data());
+ load(mo.data(), mo.size());
+}
+
+#endif // WITH_NLS
diff --git a/dggui/uitranslation.h b/dggui/uitranslation.h
new file mode 100644
index 0000000..5341255
--- /dev/null
+++ b/dggui/uitranslation.h
@@ -0,0 +1,41 @@
+/* -*- Mode: c++ -*- */
+/***************************************************************************
+ * uitranslation.h
+ *
+ * Thu May 7 18:04:40 CEST 2020
+ * Copyright 2020 Bent Bisballe Nyeng
+ * deva@aasimon.org
+ ****************************************************************************/
+
+/*
+ * This file is part of DrumGizmo.
+ *
+ * DrumGizmo is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * DrumGizmo is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with DrumGizmo; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+#pragma once
+
+#include <config.h>
+
+#include <translation.h>
+
+#ifdef WITH_NLS
+class UITranslation
+ : public Translation
+{
+public:
+ UITranslation();
+ ~UITranslation() = default;
+};
+#endif
diff --git a/dggui/utf8.cc b/dggui/utf8.cc
new file mode 100644
index 0000000..747e726
--- /dev/null
+++ b/dggui/utf8.cc
@@ -0,0 +1,361 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/***************************************************************************
+ * utf8.cc
+ *
+ * Tue Feb 27 19:18:23 CET 2007
+ * Copyright 2006 Bent Bisballe Nyeng
+ * deva@aasimon.org
+ ****************************************************************************/
+
+/*
+ * This file is part of DrumGizmo.
+ *
+ * DrumGizmo is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * DrumGizmo is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with DrumGizmo; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+#include "utf8.h"
+
+UTF8::UTF8()
+{
+ // Encode Map
+ map_encode["\x80"] = "\xc2\x80";
+ map_encode["\x81"] = "\xc2\x81";
+ map_encode["\x82"] = "\xc2\x82";
+ map_encode["\x83"] = "\xc2\x83";
+ map_encode["\x84"] = "\xc2\x84";
+ map_encode["\x85"] = "\xc2\x85";
+ map_encode["\x86"] = "\xc2\x86";
+ map_encode["\x87"] = "\xc2\x87";
+ map_encode["\x88"] = "\xc2\x88";
+ map_encode["\x89"] = "\xc2\x89";
+ map_encode["\x8a"] = "\xc2\x8a";
+ map_encode["\x8b"] = "\xc2\x8b";
+ map_encode["\x8c"] = "\xc2\x8c";
+ map_encode["\x8d"] = "\xc2\x8d";
+ map_encode["\x8e"] = "\xc2\x8e";
+ map_encode["\x8f"] = "\xc2\x8f";
+ map_encode["\x90"] = "\xc2\x90";
+ map_encode["\x91"] = "\xc2\x91";
+ map_encode["\x92"] = "\xc2\x92";
+ map_encode["\x93"] = "\xc2\x93";
+ map_encode["\x94"] = "\xc2\x94";
+ map_encode["\x95"] = "\xc2\x95";
+ map_encode["\x96"] = "\xc2\x96";
+ map_encode["\x97"] = "\xc2\x97";
+ map_encode["\x98"] = "\xc2\x98";
+ map_encode["\x99"] = "\xc2\x99";
+ map_encode["\x9a"] = "\xc2\x9a";
+ map_encode["\x9b"] = "\xc2\x9b";
+ map_encode["\x9c"] = "\xc2\x9c";
+ map_encode["\x9d"] = "\xc2\x9d";
+ map_encode["\x9e"] = "\xc2\x9e";
+ map_encode["\x9f"] = "\xc2\x9f";
+ map_encode["\xa0"] = "\xc2\xa0";
+ map_encode["\xa1"] = "\xc2\xa1";
+ map_encode["\xa2"] = "\xc2\xa2";
+ map_encode["\xa3"] = "\xc2\xa3";
+ map_encode["\xa4"] = "\xc2\xa4";
+ map_encode["\xa5"] = "\xc2\xa5";
+ map_encode["\xa6"] = "\xc2\xa6";
+ map_encode["\xa7"] = "\xc2\xa7";
+ map_encode["\xa8"] = "\xc2\xa8";
+ map_encode["\xa9"] = "\xc2\xa9";
+ map_encode["\xaa"] = "\xc2\xaa";
+ map_encode["\xab"] = "\xc2\xab";
+ map_encode["\xac"] = "\xc2\xac";
+ map_encode["\xad"] = "\xc2\xad";
+ map_encode["\xae"] = "\xc2\xae";
+ map_encode["\xaf"] = "\xc2\xaf";
+ map_encode["\xb0"] = "\xc2\xb0";
+ map_encode["\xb1"] = "\xc2\xb1";
+ map_encode["\xb2"] = "\xc2\xb2";
+ map_encode["\xb3"] = "\xc2\xb3";
+ map_encode["\xb4"] = "\xc2\xb4";
+ map_encode["\xb5"] = "\xc2\xb5";
+ map_encode["\xb6"] = "\xc2\xb6";
+ map_encode["\xb7"] = "\xc2\xb7";
+ map_encode["\xb8"] = "\xc2\xb8";
+ map_encode["\xb9"] = "\xc2\xb9";
+ map_encode["\xba"] = "\xc2\xba";
+ map_encode["\xbb"] = "\xc2\xbb";
+ map_encode["\xbc"] = "\xc2\xbc";
+ map_encode["\xbd"] = "\xc2\xbd";
+ map_encode["\xbe"] = "\xc2\xbe";
+ map_encode["\xbf"] = "\xc2\xbf";
+ map_encode["\xc0"] = "\xc3\x80";
+ map_encode["\xc1"] = "\xc3\x81";
+ map_encode["\xc2"] = "\xc3\x82";
+ map_encode["\xc3"] = "\xc3\x83";
+ map_encode["\xc4"] = "\xc3\x84";
+ map_encode["\xc5"] = "\xc3\x85";
+ map_encode["\xc6"] = "\xc3\x86";
+ map_encode["\xc7"] = "\xc3\x87";
+ map_encode["\xc8"] = "\xc3\x88";
+ map_encode["\xc9"] = "\xc3\x89";
+ map_encode["\xca"] = "\xc3\x8a";
+ map_encode["\xcb"] = "\xc3\x8b";
+ map_encode["\xcc"] = "\xc3\x8c";
+ map_encode["\xcd"] = "\xc3\x8d";
+ map_encode["\xce"] = "\xc3\x8e";
+ map_encode["\xcf"] = "\xc3\x8f";
+ map_encode["\xd0"] = "\xc3\x90";
+ map_encode["\xd1"] = "\xc3\x91";
+ map_encode["\xd2"] = "\xc3\x92";
+ map_encode["\xd3"] = "\xc3\x93";
+ map_encode["\xd4"] = "\xc3\x94";
+ map_encode["\xd5"] = "\xc3\x95";
+ map_encode["\xd6"] = "\xc3\x96";
+ map_encode["\xd7"] = "\xc3\x97";
+ map_encode["\xd8"] = "\xc3\x98";
+ map_encode["\xd9"] = "\xc3\x99";
+ map_encode["\xda"] = "\xc3\x9a";
+ map_encode["\xdb"] = "\xc3\x9b";
+ map_encode["\xdc"] = "\xc3\x9c";
+ map_encode["\xdd"] = "\xc3\x9d";
+ map_encode["\xde"] = "\xc3\x9e";
+ map_encode["\xdf"] = "\xc3\x9f";
+ map_encode["\xe0"] = "\xc3\xa0";
+ map_encode["\xe1"] = "\xc3\xa1";
+ map_encode["\xe2"] = "\xc3\xa2";
+ map_encode["\xe3"] = "\xc3\xa3";
+ map_encode["\xe4"] = "\xc3\xa4";
+ map_encode["\xe5"] = "\xc3\xa5";
+ map_encode["\xe6"] = "\xc3\xa6";
+ map_encode["\xe7"] = "\xc3\xa7";
+ map_encode["\xe8"] = "\xc3\xa8";
+ map_encode["\xe9"] = "\xc3\xa9";
+ map_encode["\xea"] = "\xc3\xaa";
+ map_encode["\xeb"] = "\xc3\xab";
+ map_encode["\xec"] = "\xc3\xac";
+ map_encode["\xed"] = "\xc3\xad";
+ map_encode["\xee"] = "\xc3\xae";
+ map_encode["\xef"] = "\xc3\xaf";
+ map_encode["\xf0"] = "\xc3\xb0";
+ map_encode["\xf1"] = "\xc3\xb1";
+ map_encode["\xf2"] = "\xc3\xb2";
+ map_encode["\xf3"] = "\xc3\xb3";
+ map_encode["\xf4"] = "\xc3\xb4";
+ map_encode["\xf5"] = "\xc3\xb5";
+ map_encode["\xf6"] = "\xc3\xb6";
+ map_encode["\xf7"] = "\xc3\xb7";
+ map_encode["\xf8"] = "\xc3\xb8";
+ map_encode["\xf9"] = "\xc3\xb9";
+ map_encode["\xfa"] = "\xc3\xba";
+ map_encode["\xfb"] = "\xc3\xbb";
+ map_encode["\xfc"] = "\xc3\xbc";
+ map_encode["\xfd"] = "\xc3\xbd";
+ map_encode["\xfe"] = "\xc3\xbe";
+ map_encode["\xff"] = "\xc3\xbf";
+
+ // Decode Map
+ map_decode["\xc2\x80"] = "\x80";
+ map_decode["\xc2\x81"] = "\x81";
+ map_decode["\xc2\x82"] = "\x82";
+ map_decode["\xc2\x83"] = "\x83";
+ map_decode["\xc2\x84"] = "\x84";
+ map_decode["\xc2\x85"] = "\x85";
+ map_decode["\xc2\x86"] = "\x86";
+ map_decode["\xc2\x87"] = "\x87";
+ map_decode["\xc2\x88"] = "\x88";
+ map_decode["\xc2\x89"] = "\x89";
+ map_decode["\xc2\x8a"] = "\x8a";
+ map_decode["\xc2\x8b"] = "\x8b";
+ map_decode["\xc2\x8c"] = "\x8c";
+ map_decode["\xc2\x8d"] = "\x8d";
+ map_decode["\xc2\x8e"] = "\x8e";
+ map_decode["\xc2\x8f"] = "\x8f";
+ map_decode["\xc2\x90"] = "\x90";
+ map_decode["\xc2\x91"] = "\x91";
+ map_decode["\xc2\x92"] = "\x92";
+ map_decode["\xc2\x93"] = "\x93";
+ map_decode["\xc2\x94"] = "\x94";
+ map_decode["\xc2\x95"] = "\x95";
+ map_decode["\xc2\x96"] = "\x96";
+ map_decode["\xc2\x97"] = "\x97";
+ map_decode["\xc2\x98"] = "\x98";
+ map_decode["\xc2\x99"] = "\x99";
+ map_decode["\xc2\x9a"] = "\x9a";
+ map_decode["\xc2\x9b"] = "\x9b";
+ map_decode["\xc2\x9c"] = "\x9c";
+ map_decode["\xc2\x9d"] = "\x9d";
+ map_decode["\xc2\x9e"] = "\x9e";
+ map_decode["\xc2\x9f"] = "\x9f";
+ map_decode["\xc2\xa0"] = "\xa0";
+ map_decode["\xc2\xa1"] = "\xa1";
+ map_decode["\xc2\xa2"] = "\xa2";
+ map_decode["\xc2\xa3"] = "\xa3";
+ map_decode["\xc2\xa4"] = "\xa4";
+ map_decode["\xc2\xa5"] = "\xa5";
+ map_decode["\xc2\xa6"] = "\xa6";
+ map_decode["\xc2\xa7"] = "\xa7";
+ map_decode["\xc2\xa8"] = "\xa8";
+ map_decode["\xc2\xa9"] = "\xa9";
+ map_decode["\xc2\xaa"] = "\xaa";
+ map_decode["\xc2\xab"] = "\xab";
+ map_decode["\xc2\xac"] = "\xac";
+ map_decode["\xc2\xad"] = "\xad";
+ map_decode["\xc2\xae"] = "\xae";
+ map_decode["\xc2\xaf"] = "\xaf";
+ map_decode["\xc2\xb0"] = "\xb0";
+ map_decode["\xc2\xb1"] = "\xb1";
+ map_decode["\xc2\xb2"] = "\xb2";
+ map_decode["\xc2\xb3"] = "\xb3";
+ map_decode["\xc2\xb4"] = "\xb4";
+ map_decode["\xc2\xb5"] = "\xb5";
+ map_decode["\xc2\xb6"] = "\xb6";
+ map_decode["\xc2\xb7"] = "\xb7";
+ map_decode["\xc2\xb8"] = "\xb8";
+ map_decode["\xc2\xb9"] = "\xb9";
+ map_decode["\xc2\xba"] = "\xba";
+ map_decode["\xc2\xbb"] = "\xbb";
+ map_decode["\xc2\xbc"] = "\xbc";
+ map_decode["\xc2\xbd"] = "\xbd";
+ map_decode["\xc2\xbe"] = "\xbe";
+ map_decode["\xc2\xbf"] = "\xbf";
+ map_decode["\xc3\x80"] = "\xc0";
+ map_decode["\xc3\x81"] = "\xc1";
+ map_decode["\xc3\x82"] = "\xc2";
+ map_decode["\xc3\x83"] = "\xc3";
+ map_decode["\xc3\x84"] = "\xc4";
+ map_decode["\xc3\x85"] = "\xc5";
+ map_decode["\xc3\x86"] = "\xc6";
+ map_decode["\xc3\x87"] = "\xc7";
+ map_decode["\xc3\x88"] = "\xc8";
+ map_decode["\xc3\x89"] = "\xc9";
+ map_decode["\xc3\x8a"] = "\xca";
+ map_decode["\xc3\x8b"] = "\xcb";
+ map_decode["\xc3\x8c"] = "\xcc";
+ map_decode["\xc3\x8d"] = "\xcd";
+ map_decode["\xc3\x8e"] = "\xce";
+ map_decode["\xc3\x8f"] = "\xcf";
+ map_decode["\xc3\x90"] = "\xd0";
+ map_decode["\xc3\x91"] = "\xd1";
+ map_decode["\xc3\x92"] = "\xd2";
+ map_decode["\xc3\x93"] = "\xd3";
+ map_decode["\xc3\x94"] = "\xd4";
+ map_decode["\xc3\x95"] = "\xd5";
+ map_decode["\xc3\x96"] = "\xd6";
+ map_decode["\xc3\x97"] = "\xd7";
+ map_decode["\xc3\x98"] = "\xd8";
+ map_decode["\xc3\x99"] = "\xd9";
+ map_decode["\xc3\x9a"] = "\xda";
+ map_decode["\xc3\x9b"] = "\xdb";
+ map_decode["\xc3\x9c"] = "\xdc";
+ map_decode["\xc3\x9d"] = "\xdd";
+ map_decode["\xc3\x9e"] = "\xde";
+ map_decode["\xc3\x9f"] = "\xdf";
+ map_decode["\xc3\xa0"] = "\xe0";
+ map_decode["\xc3\xa1"] = "\xe1";
+ map_decode["\xc3\xa2"] = "\xe2";
+ map_decode["\xc3\xa3"] = "\xe3";
+ map_decode["\xc3\xa4"] = "\xe4";
+ map_decode["\xc3\xa5"] = "\xe5";
+ map_decode["\xc3\xa6"] = "\xe6";
+ map_decode["\xc3\xa7"] = "\xe7";
+ map_decode["\xc3\xa8"] = "\xe8";
+ map_decode["\xc3\xa9"] = "\xe9";
+ map_decode["\xc3\xaa"] = "\xea";
+ map_decode["\xc3\xab"] = "\xeb";
+ map_decode["\xc3\xac"] = "\xec";
+ map_decode["\xc3\xad"] = "\xed";
+ map_decode["\xc3\xae"] = "\xee";
+ map_decode["\xc3\xaf"] = "\xef";
+ map_decode["\xc3\xb0"] = "\xf0";
+ map_decode["\xc3\xb1"] = "\xf1";
+ map_decode["\xc3\xb2"] = "\xf2";
+ map_decode["\xc3\xb3"] = "\xf3";
+ map_decode["\xc3\xb4"] = "\xf4";
+ map_decode["\xc3\xb5"] = "\xf5";
+ map_decode["\xc3\xb6"] = "\xf6";
+ map_decode["\xc3\xb7"] = "\xf7";
+ map_decode["\xc3\xb8"] = "\xf8";
+ map_decode["\xc3\xb9"] = "\xf9";
+ map_decode["\xc3\xba"] = "\xfa";
+ map_decode["\xc3\xbb"] = "\xfb";
+ map_decode["\xc3\xbc"] = "\xfc";
+ map_decode["\xc3\xbd"] = "\xfd";
+ map_decode["\xc3\xbe"] = "\xfe";
+ map_decode["\xc3\xbf"] = "\xff";
+ // FIXME: This is just a hack to make Goran Mekic's name work.
+ map_decode["\xc4\x87"] = "c";
+}
+
+std::string UTF8::fromLatin1(std::string const& s)
+{
+ std::string ret;
+
+ for(int i = 0; i < (int)s.length(); i++)
+ {
+ std::string c;
+
+ if((unsigned char)s[i] <= 0x7F)
+ {
+ c = s.substr(i, 1);
+ }
+ else
+ {
+ c = map_encode[s.substr(i, 1)];
+ }
+
+ // If c == "", the character wasn't found in the map.
+ // Ignore this case for now and just push an empty string in this case.
+
+ ret.append(c);
+ }
+
+ return ret;
+}
+
+std::string UTF8::toLatin1(std::string const& s)
+{
+ std::string ret;
+
+ int width = 1;
+ for(int i = 0; i < (int)s.length(); i += width)
+ {
+ if(/*(unsigned char)s[i]>=0x00&&*/ (unsigned char)s[i] <= 0x7F)
+ {
+ width = 1; // 00-7F -> 1 byte
+ }
+ if((unsigned char)s[i] >= 0xC2 && (unsigned char)s[i] <= 0xDF)
+ {
+ width = 2; // C2-DF -> 2 bytes
+ }
+ if((unsigned char)s[i] >= 0xE0 && (unsigned char)s[i] <= 0xEF)
+ {
+ width = 3; // E0-EF -> 3 bytes
+ }
+ if((unsigned char)s[i] >= 0xF0 && (unsigned char)s[i] <= 0xF4)
+ {
+ width = 4; // F0-F4 -> 4 bytes
+ }
+
+ std::string c;
+ if(width == 1)
+ {
+ c = s.substr(i, 1);
+ }
+ else
+ {
+ c = map_decode[s.substr(i, width)];
+ }
+
+ // If c == "", the character wasn't found in the map.
+ // Ignore this case for now and just push an empty string in this case.
+
+ ret.append(c);
+ }
+
+ return ret;
+}
diff --git a/dggui/utf8.h b/dggui/utf8.h
new file mode 100644
index 0000000..04c26b1
--- /dev/null
+++ b/dggui/utf8.h
@@ -0,0 +1,47 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/***************************************************************************
+ * utf8.h
+ *
+ * Tue Feb 27 19:18:23 CET 2007
+ * Copyright 2006 Bent Bisballe Nyeng
+ * deva@aasimon.org
+ ****************************************************************************/
+
+/*
+ * This file is part of DrumGizmo.
+ *
+ * DrumGizmo is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * DrumGizmo is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with DrumGizmo; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+#pragma once
+
+#include <string>
+#include <unordered_map>
+
+// Class to convert utf8 to latin1 and the other way around.
+class UTF8
+{
+public:
+ UTF8();
+
+ // Encode a string from latin1 to UTF-8.
+ std::string fromLatin1(std::string const& s);
+
+ // Decode a string from UTF-8 to latin1.
+ std::string toLatin1(std::string const& s);
+
+private:
+ std::unordered_map<std::string, std::string> map_encode;
+ std::unordered_map<std::string, std::string> map_decode;
+};
diff --git a/dggui/verticalline.cc b/dggui/verticalline.cc
new file mode 100644
index 0000000..6a3a98a
--- /dev/null
+++ b/dggui/verticalline.cc
@@ -0,0 +1,51 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/***************************************************************************
+ * verticalline.cc
+ *
+ * Sat Apr 6 12:59:44 CEST 2013
+ * Copyright 2013 Bent Bisballe Nyeng
+ * deva@aasimon.org
+ ****************************************************************************/
+
+/*
+ * This file is part of DrumGizmo.
+ *
+ * DrumGizmo is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * DrumGizmo is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with DrumGizmo; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+#include "verticalline.h"
+
+#include "painter.h"
+
+namespace GUI {
+
+VerticalLine::VerticalLine(Widget *parent)
+ : Widget(parent)
+ , vline(":resources/vertline.png")
+{
+}
+
+void VerticalLine::repaintEvent(RepaintEvent* repaintEvent)
+{
+ if(height() < 2)
+ {
+ return;
+ }
+
+ Painter p(*this);
+ p.drawImageStretched(0, (height() - vline.height()) / 2,
+ vline, width(), vline.height());
+}
+
+} // GUI::
diff --git a/dggui/verticalline.h b/dggui/verticalline.h
new file mode 100644
index 0000000..3403244
--- /dev/null
+++ b/dggui/verticalline.h
@@ -0,0 +1,47 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/***************************************************************************
+ * verticalline.h
+ *
+ * Sat Apr 6 12:59:43 CEST 2013
+ * Copyright 2013 Bent Bisballe Nyeng
+ * deva@aasimon.org
+ ****************************************************************************/
+
+/*
+ * This file is part of DrumGizmo.
+ *
+ * DrumGizmo is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * DrumGizmo is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with DrumGizmo; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+#pragma once
+
+#include "widget.h"
+#include "image.h"
+
+namespace GUI {
+
+class VerticalLine : public Widget {
+public:
+ VerticalLine(Widget* parent);
+ virtual ~VerticalLine() = default;
+
+protected:
+ // From Widget:
+ virtual void repaintEvent(RepaintEvent* repaintEvent) override;
+
+private:
+ Image vline;
+};
+
+} // GUI::
diff --git a/dggui/widget.cc b/dggui/widget.cc
new file mode 100644
index 0000000..da6b1e2
--- /dev/null
+++ b/dggui/widget.cc
@@ -0,0 +1,297 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/***************************************************************************
+ * widget.cc
+ *
+ * Sun Oct 9 13:01:44 CEST 2011
+ * Copyright 2011 Bent Bisballe Nyeng
+ * deva@aasimon.org
+ ****************************************************************************/
+
+/*
+ * This file is part of DrumGizmo.
+ *
+ * DrumGizmo is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * DrumGizmo is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with DrumGizmo; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+#include "widget.h"
+
+#include <cassert>
+
+#include "painter.h"
+#include "window.h"
+
+namespace GUI
+{
+
+Widget::Widget(Widget* parent)
+ : parent(parent)
+{
+ if(parent)
+ {
+ parent->addChild(this);
+ _window = parent->window();
+ }
+
+ pixbuf.x = translateToWindowX();
+ pixbuf.y = translateToWindowY();
+}
+
+Widget::~Widget()
+{
+ if(parent)
+ {
+ parent->removeChild(this);
+ }
+}
+
+void Widget::show()
+{
+ setVisible(true);
+}
+
+void Widget::hide()
+{
+ setVisible(false);
+}
+
+void Widget::setVisible(bool visible)
+{
+ _visible = visible;
+ pixbuf.visible = visible;
+ redraw();
+}
+
+bool Widget::visible() const
+{
+ return _visible;
+}
+
+void Widget::redraw()
+{
+ dirty = true;
+ window()->needsRedraw();
+}
+
+void Widget::addChild(Widget* widget)
+{
+ children.push_back(widget);
+}
+
+void Widget::removeChild(Widget* widget)
+{
+ for(auto i = children.begin(); i != children.end(); ++i)
+ {
+ if(*i == widget)
+ {
+ children.erase(i);
+ return;
+ }
+ }
+}
+
+void Widget::reparent(Widget* parent)
+{
+ if(parent == this->parent)
+ {
+ return; // Already at the right parent.
+ }
+
+ if(this->parent)
+ {
+ this->parent->removeChild(this);
+ }
+
+ if(parent)
+ {
+ parent->addChild(this);
+ }
+
+ this->parent = parent;
+}
+
+void Widget::resize(std::size_t width, std::size_t height)
+{
+ assert(width < 32000 && height < 32000); // Catch negative values as size_t
+ if((width < 1) || (height < 1) ||
+ ((width == _width) && (height == _height)))
+ {
+ return;
+ }
+
+ _width = width;
+ _height = height;
+
+ // Store old size/position in pixelbuffer for rendering invalidation.
+ if(!pixbuf.has_last)
+ {
+ pixbuf.last_width = pixbuf.width;
+ pixbuf.last_height = pixbuf.height;
+ pixbuf.last_x = pixbuf.x;
+ pixbuf.last_y = pixbuf.y;
+ pixbuf.has_last = true;
+ }
+
+ pixbuf.realloc(width, height);
+ pixbuf.x = translateToWindowX();
+ pixbuf.y = translateToWindowY();
+ redraw();
+ sizeChangeNotifier(width, height);
+}
+
+void Widget::move(int x, int y)
+{
+ if((_x == x) &&
+ (_y == y))
+ {
+ return;
+ }
+
+ _x = x;
+ _y = y;
+
+ // Store old size/position in pixelbuffer for rendering invalidation.
+ if(!pixbuf.has_last)
+ {
+ pixbuf.last_width = pixbuf.width;
+ pixbuf.last_height = pixbuf.height;
+ pixbuf.last_x = pixbuf.x;
+ pixbuf.last_y = pixbuf.y;
+ pixbuf.has_last = true;
+ }
+
+ //pixbuf.x = translateToWindowX();
+ //pixbuf.y = translateToWindowY();
+
+ positionChangeNotifier(x, y);
+}
+
+int Widget::x() const
+{
+ return _x;
+}
+
+int Widget::y() const
+{
+ return _y;
+}
+
+std::size_t Widget::width() const
+{
+ return _width;
+}
+
+std::size_t Widget::height() const
+{
+ return _height;
+}
+
+Point Widget::position() const
+{
+ return { _x, _y };
+}
+
+PixelBufferAlpha& Widget::getPixelBuffer()
+{
+ return pixbuf;
+}
+
+ImageCache& Widget::getImageCache()
+{
+ assert(parent);
+ return parent->getImageCache();
+}
+
+Widget* Widget::find(int x, int y)
+{
+ for(auto i = children.rbegin(); i != children.rend(); ++i)
+ {
+ Widget* widget = *i;
+ if(widget->visible())
+ {
+ if((x >= widget->x()) && (x < (widget->x() + (int)widget->width())) &&
+ (y >= widget->y()) && (y < (widget->y() + (int)widget->height())))
+ {
+ return widget->find(x - widget->x(), y - widget->y());
+ }
+ }
+ }
+
+ return this;
+}
+
+Window* Widget::window()
+{
+ return _window;
+}
+
+std::vector<PixelBufferAlpha*> Widget::getPixelBuffers()
+{
+ std::vector<PixelBufferAlpha*> pixelBuffers;
+
+ pixbuf.x = translateToWindowX();
+ pixbuf.y = translateToWindowY();
+
+ if(dirty)
+ {
+ repaintEvent(nullptr);
+ pixbuf.dirty = true;
+ dirty = false;
+ }
+
+ if(pixbuf.dirty || visible())
+ {
+ pixelBuffers.push_back(&pixbuf);
+ }
+
+ if(visible())
+ {
+ for(auto child : children)
+ {
+ auto childPixelBuffers = child->getPixelBuffers();
+ pixelBuffers.insert(pixelBuffers.end(),
+ childPixelBuffers.begin(), childPixelBuffers.end());
+ }
+ }
+
+ return pixelBuffers;
+}
+
+bool Widget::hasKeyboardFocus()
+{
+ return window()->keyboardFocus() == this;
+}
+
+std::size_t Widget::translateToWindowX()
+{
+ size_t window_x = x();
+ if(parent)
+ {
+ window_x += parent->translateToWindowX();
+ }
+
+ return window_x;
+}
+
+std::size_t Widget::translateToWindowY()
+{
+ size_t window_y = y();
+ if(parent)
+ {
+ window_y += parent->translateToWindowY();
+ }
+
+ return window_y;
+}
+
+} // GUI::
diff --git a/dggui/widget.h b/dggui/widget.h
new file mode 100644
index 0000000..b9436b7
--- /dev/null
+++ b/dggui/widget.h
@@ -0,0 +1,139 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/***************************************************************************
+ * widget.h
+ *
+ * Sun Oct 9 13:01:44 CEST 2011
+ * Copyright 2011 Bent Bisballe Nyeng
+ * deva@aasimon.org
+ ****************************************************************************/
+
+/*
+ * This file is part of DrumGizmo.
+ *
+ * DrumGizmo is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * DrumGizmo is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with DrumGizmo; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+#pragma once
+
+#include "guievent.h"
+#include "pixelbuffer.h"
+#include "notifier.h"
+#include "layout.h"
+#include "canvas.h"
+
+#include <vector>
+
+namespace GUI
+{
+
+struct Point
+{
+ int x;
+ int y;
+};
+
+struct Size
+{
+ std::size_t width;
+ std::size_t height;
+};
+
+class ImageCache;
+class Window;
+
+class Widget
+ : public Listener
+ , public LayoutItem
+ , public Canvas
+{
+ friend class Painter;
+public:
+ Widget(Widget* parent);
+ virtual ~Widget();
+
+ virtual void show();
+ virtual void hide();
+ void setVisible(bool visible);
+ virtual bool visible() const;
+
+ //! Mark widget dirty and shedule redraw on next window redraw.
+ void redraw();
+
+ // From LayoutItem
+ virtual void resize(std::size_t width, std::size_t height) override;
+ virtual void move(int x, int y) override;
+ virtual int x() const override;
+ virtual int y() const override;
+ virtual std::size_t width() const override;
+ virtual std::size_t height() const override;
+
+ Point position() const;
+
+ // From Canvas
+ PixelBufferAlpha& getPixelBuffer() override;
+
+ virtual bool isFocusable() { return false; }
+ virtual bool catchMouse() { return false; }
+
+ void addChild(Widget* widget);
+ void removeChild(Widget* widget);
+ void reparent(Widget* parent);
+
+ virtual void repaintEvent(RepaintEvent* repaintEvent) {}
+ virtual void mouseMoveEvent(MouseMoveEvent* mouseMoveEvent) {}
+ virtual void buttonEvent(ButtonEvent* buttonEvent) {}
+ virtual void scrollEvent(ScrollEvent* scrollEvent) {}
+ virtual void keyEvent(KeyEvent* keyEvent) {}
+ virtual void mouseLeaveEvent() {}
+ virtual void mouseEnterEvent() {}
+
+ virtual ImageCache& getImageCache();
+
+ Widget* find(int x, int y);
+
+ virtual Window* window();
+
+ std::vector<PixelBufferAlpha*> getPixelBuffers();
+
+ bool hasKeyboardFocus();
+
+ Notifier<std::size_t, std::size_t> sizeChangeNotifier; // (width, height)
+ Notifier<int, int> positionChangeNotifier; // (x, y)
+
+ //! Translate x-coordinate from parent-space to window-space.
+ virtual std::size_t translateToWindowX();
+
+ //! Translate y-coordinate from parent-space to window-space.
+ virtual std::size_t translateToWindowY();
+
+protected:
+ friend class EventHandler;
+ PixelBufferAlpha pixbuf{0,0};
+
+ std::vector<Widget*> children;
+
+ Widget* parent = nullptr;
+ Window* _window = nullptr;
+
+ int _x{0};
+ int _y{0};
+ std::size_t _width{0};
+ std::size_t _height{0};
+
+ bool _visible{true};
+
+ bool dirty{true};
+};
+
+} // GUI::
diff --git a/dggui/window.cc b/dggui/window.cc
new file mode 100644
index 0000000..5e0ad31
--- /dev/null
+++ b/dggui/window.cc
@@ -0,0 +1,266 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/***************************************************************************
+ * window.cc
+ *
+ * Sun Oct 9 13:11:53 CEST 2011
+ * Copyright 2011 Bent Bisballe Nyeng
+ * deva@aasimon.org
+ ****************************************************************************/
+
+/*
+ * This file is part of DrumGizmo.
+ *
+ * DrumGizmo is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * DrumGizmo is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with DrumGizmo; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+#include "window.h"
+
+#include <cstring>
+
+#include "painter.h"
+
+#ifndef UI_PUGL
+#ifdef UI_X11
+#include "nativewindow_x11.h"
+#endif // UI_X11
+#ifdef UI_WIN32
+#include "nativewindow_win32.h"
+#endif // UI_WIN32
+#ifdef UI_COCOA
+#include "nativewindow_cocoa.h"
+#endif // UI_COCOA
+#else
+#include "nativewindow_pugl.h"
+#endif // !UI_PUGL
+
+namespace GUI
+{
+
+Window::Window(void* native_window)
+ : Widget(nullptr)
+ , wpixbuf(1, 1)
+{
+ // Make sure we have a valid size when initialising the NativeWindow
+ _width = wpixbuf.width;
+ _height = wpixbuf.height;
+
+#ifndef UI_PUGL
+#ifdef UI_X11
+ native = new NativeWindowX11(native_window, *this);
+#endif // UI_X11
+#ifdef UI_WIN32
+ native = new NativeWindowWin32(native_window, *this);
+#endif // UI_WIN32
+#ifdef UI_COCOA
+ native = new NativeWindowCocoa(native_window, *this);
+#endif // UI_COCOA
+#else
+ // Use pugl
+ native = new NativeWindowPugl(native_window, *this);
+#endif // !UI_PUGL
+
+ eventhandler = new EventHandler(*native, *this);
+
+ setVisible(true); // The root widget is always visible.
+}
+
+Window::~Window()
+{
+ delete native;
+ delete eventhandler;
+}
+
+void Window::setFixedSize(int w, int h)
+{
+ native->setFixedSize(w, h);
+}
+
+void Window::setAlwaysOnTop(bool always_on_top)
+{
+ native->setAlwaysOnTop(always_on_top);
+}
+
+void Window::setCaption(const std::string& caption)
+{
+ native->setCaption(caption);
+}
+
+//! This overload the resize method on Widget and simply requests a window resize
+//! on the windowmanager/OS. The resized() method is called by the event handler
+//! once the window has been resized.
+void Window::resize(std::size_t width, std::size_t height)
+{
+ native->resize(width, height);
+}
+
+//! This overload the move method on Widget and simply requests a window move
+//! on the windowmanager/OS. The moved() method is called by the event handler
+//! once the window has been moved.
+void Window::move(int x, int y)
+{
+ native->move(x, y);
+}
+
+void Window::show()
+{
+ Widget::show();
+ redraw();
+ native->show();
+}
+
+void Window::hide()
+{
+ native->hide();
+ Widget::hide();
+}
+
+Window* Window::window()
+{
+ return this;
+}
+
+Size Window::getNativeSize()
+{
+ auto sz = native->getSize();
+ return {sz.first, sz.second};
+}
+
+ImageCache& Window::getImageCache()
+{
+ return image_cache;
+}
+
+EventHandler* Window::eventHandler()
+{
+ return eventhandler;
+}
+
+Widget* Window::keyboardFocus()
+{
+ return _keyboardFocus;
+}
+
+void Window::setKeyboardFocus(Widget* widget)
+{
+ auto oldFocusWidget = _keyboardFocus;
+ _keyboardFocus = widget;
+
+ if(oldFocusWidget)
+ {
+ oldFocusWidget->redraw();
+ }
+
+ if(_keyboardFocus)
+ {
+ _keyboardFocus->redraw();
+ }
+}
+
+Widget* Window::buttonDownFocus()
+{
+ return _buttonDownFocus;
+}
+
+void Window::setButtonDownFocus(Widget* widget)
+{
+ _buttonDownFocus = widget;
+ native->grabMouse(widget != nullptr);
+}
+
+Widget* Window::mouseFocus()
+{
+ return _mouseFocus;
+}
+
+void Window::setMouseFocus(Widget* widget)
+{
+ _mouseFocus = widget;
+
+}
+
+void Window::needsRedraw()
+{
+ needs_redraw = true;
+}
+
+void* Window::getNativeWindowHandle() const
+{
+ return native->getNativeWindowHandle();
+}
+
+Point Window::translateToScreen(const Point& point)
+{
+ return native->translateToScreen(point);
+}
+
+std::size_t Window::translateToWindowX()
+{
+ return 0;
+}
+
+std::size_t Window::translateToWindowY()
+{
+ return 0;
+}
+
+//! Called by event handler when an windowmanager/OS window resize event has
+//! been received. Do not call this directly.
+void Window::resized(std::size_t width, std::size_t height)
+{
+ auto size = native->getSize();
+ if((wpixbuf.width != size.first) ||
+ (wpixbuf.height != size.second))
+ {
+ wpixbuf.realloc(size.first, size.second);
+ Widget::resize(size.first, size.second);
+ }
+
+ updateBuffer();
+}
+
+//! Called by event handler when an windowmanager/OS window move event has
+//! been received. Do not call this directly.
+void Window::moved(int x, int y)
+{
+ // Make sure widget coordinates are updated.
+ Widget::move(x, y);
+}
+
+bool Window::updateBuffer()
+{
+ if(!native)
+ {
+ return false;
+ }
+
+ if(!needs_redraw)
+ {
+ // Nothing changed, don't update anything.
+ return false;
+ }
+
+ auto pixel_buffers = getPixelBuffers();
+
+ auto dirty_rect = wpixbuf.updateBuffer(pixel_buffers);
+
+ if(!dirty_rect.empty())
+ {
+ native->redraw(dirty_rect);
+ }
+ needs_redraw = false;
+
+ return true;
+}
+
+} // GUI::
diff --git a/dggui/window.h b/dggui/window.h
new file mode 100644
index 0000000..6031500
--- /dev/null
+++ b/dggui/window.h
@@ -0,0 +1,119 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/***************************************************************************
+ * window.h
+ *
+ * Sun Oct 9 13:11:52 CEST 2011
+ * Copyright 2011 Bent Bisballe Nyeng
+ * deva@aasimon.org
+ ****************************************************************************/
+
+/*
+ * This file is part of DrumGizmo.
+ *
+ * DrumGizmo is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * DrumGizmo is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with DrumGizmo; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+#pragma once
+
+#include "widget.h"
+
+#include "pixelbuffer.h"
+#include "nativewindow.h"
+#include "image.h"
+#include "eventhandler.h"
+#include "imagecache.h"
+
+namespace GUI
+{
+
+class Window
+ : public Widget
+{
+public:
+ Window(void* native_window = nullptr);
+ ~Window();
+
+ void setFixedSize(int width, int height);
+ void setAlwaysOnTop(bool always_on_top);
+ void setCaption(const std::string& caption);
+
+ // From Widget:
+ void resize(std::size_t width, std::size_t height) override;
+ void move(int x, int y) override;
+ void show() override;
+ void hide() override;
+ Window* window() override;
+ Size getNativeSize();
+ ImageCache& getImageCache() override;
+
+ EventHandler* eventHandler();
+
+ Widget* keyboardFocus();
+ void setKeyboardFocus(Widget* widget);
+
+ Widget* buttonDownFocus();
+ void setButtonDownFocus(Widget* widget);
+
+ Widget* mouseFocus();
+ void setMouseFocus(Widget* widget);
+
+ //! Tag the window buffer dirty to be rendered.
+ void needsRedraw();
+
+ // \returns the native window handle, it HWND on Win32 or Window id on X11
+ void* getNativeWindowHandle() const;
+
+ //! Translate a local window coordinate to a global screen coordinate.
+ Point translateToScreen(const Point& point);
+
+protected:
+ // For the EventHandler
+ friend class EventHandler;
+
+ // From Widget:
+ std::size_t translateToWindowX() override;
+ std::size_t translateToWindowY() override;
+ void resized(std::size_t width, std::size_t height);
+ void moved(int x, int y);
+
+ //! Returns true if window pixel buffer changed and needs to be copied to
+ //! native window.
+ bool updateBuffer();
+
+ // For the Painter
+ friend class Widget;
+
+ // For the NativeWindow implementations:
+ friend class NativeWindowX11;
+ friend class NativeWindowWin32;
+ friend class NativeWindowPugl;
+ friend class NativeWindowCocoa;
+ PixelBuffer wpixbuf;
+
+ size_t refcount{0};
+
+ Widget* _keyboardFocus{nullptr};
+ Widget* _buttonDownFocus{nullptr};
+ Widget* _mouseFocus{nullptr};
+
+ NativeWindow* native{nullptr};
+ EventHandler* eventhandler{nullptr};
+
+ size_t maxRefcount{0};
+
+ bool needs_redraw{false};
+ ImageCache image_cache;
+};
+
+} // GUI::