diff options
Diffstat (limited to 'dggui')
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:: |