diff options
Diffstat (limited to 'dggui')
97 files changed, 14409 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..39e9eaa --- /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 dggui +{ + +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); +} + +} // dggui:: diff --git a/dggui/button.h b/dggui/button.h new file mode 100644 index 0000000..f1b6584 --- /dev/null +++ b/dggui/button.h @@ -0,0 +1,65 @@ +/* -*- 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 dggui +{ + +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"}; +}; + +} // dggui:: diff --git a/dggui/button_base.cc b/dggui/button_base.cc new file mode 100644 index 0000000..4625b55 --- /dev/null +++ b/dggui/button_base.cc @@ -0,0 +1,117 @@ +/* -*- 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 dggui +{ + +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(); +	} +} + +} // dggui:: diff --git a/dggui/button_base.h b/dggui/button_base.h new file mode 100644 index 0000000..72f693b --- /dev/null +++ b/dggui/button_base.h @@ -0,0 +1,79 @@ +/* -*- 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 dggui +{ + +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}; +}; + +} // dggui:: diff --git a/dggui/canvas.h b/dggui/canvas.h new file mode 100644 index 0000000..1b0b5b2 --- /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 dggui +{ + +//! 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; +}; + +} // dggui:: diff --git a/dggui/checkbox.cc b/dggui/checkbox.cc new file mode 100644 index 0000000..8911862 --- /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 dggui +{ + +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); +	} +} + +} // dggui:: diff --git a/dggui/checkbox.h b/dggui/checkbox.h new file mode 100644 index 0000000..1a323b6 --- /dev/null +++ b/dggui/checkbox.h @@ -0,0 +1,50 @@ +/* -*- 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 dggui +{ + +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; +}; + +} // dggui:: diff --git a/dggui/colour.cc b/dggui/colour.cc new file mode 100644 index 0000000..6d510f9 --- /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 dggui +{ + +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); +} + +} // dggui:: diff --git a/dggui/colour.h b/dggui/colour.h new file mode 100644 index 0000000..aea286c --- /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 dggui +{ + +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}}; +}; + +} // dggui:: diff --git a/dggui/combobox.cc b/dggui/combobox.cc new file mode 100644 index 0000000..33765e2 --- /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 dggui +{ + +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()); +} + +} // dggui:: diff --git a/dggui/combobox.h b/dggui/combobox.h new file mode 100644 index 0000000..a3ef8ac --- /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 dggui +{ + +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; +}; + +} // dggui:: diff --git a/dggui/dialog.cc b/dggui/dialog.cc new file mode 100644 index 0000000..74d9200 --- /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 dggui +{ + +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; +} + +} // dggui:: diff --git a/dggui/dialog.h b/dggui/dialog.h new file mode 100644 index 0000000..a9911c5 --- /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 dggui +{ + +//! 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}; +}; + +} // dggui:: diff --git a/dggui/drawable.h b/dggui/drawable.h new file mode 100644 index 0000000..84e33db --- /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 dggui +{ + +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; +}; + +} // dggui:: diff --git a/dggui/eventhandler.cc b/dggui/eventhandler.cc new file mode 100644 index 0000000..b2cf616 --- /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 dggui +{ + +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); +} + + +} // dggui:: diff --git a/dggui/eventhandler.h b/dggui/eventhandler.h new file mode 100644 index 0000000..6a2b48f --- /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 dggui +{ + +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; +}; + +} // dggui:: diff --git a/dggui/font.cc b/dggui/font.cc new file mode 100644 index 0000000..e59cb41 --- /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 dggui +{ + +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; +} + +} // dggui:: diff --git a/dggui/font.h b/dggui/font.h new file mode 100644 index 0000000..6ce1f5c --- /dev/null +++ b/dggui/font.h @@ -0,0 +1,65 @@ +/* -*- 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 dggui +{ + +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}; +}; + +} // dggui:: diff --git a/dggui/frame.cc b/dggui/frame.cc new file mode 100644 index 0000000..3a163f1 --- /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 dggui +{ + +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()); +} + +} // dggui:: diff --git a/dggui/frame.h b/dggui/frame.h new file mode 100644 index 0000000..dc8e17a --- /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 dggui +{ + +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; +	dggui::Colour label_colour{0.1}; +	dggui::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; +	dggui::Colour grey_box_colour{0.7}; +	dggui::Colour grey_box_colour_disabled{0.7}; +	dggui::Colour background_colour{0.85, 0.8}; + +	// +	// content +	// + +	// content frame +	dggui::Colour frame_colour_top{0.95}; +	dggui::Colour frame_colour_bottom{0.4}; +	dggui::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; +}; + +} // dggui:: diff --git a/dggui/guievent.h b/dggui/guievent.h new file mode 100644 index 0000000..aae0ae1 --- /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 dggui +{ + +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; +	} +}; + +} // dggui:: diff --git a/dggui/helpbutton.cc b/dggui/helpbutton.cc new file mode 100644 index 0000000..cc03112 --- /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 dggui +{ + +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(); +} + +} // dggui:: diff --git a/dggui/helpbutton.h b/dggui/helpbutton.h new file mode 100644 index 0000000..639799a --- /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 dggui +{ + +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; +}; + +} // dggui:: diff --git a/dggui/image.cc b/dggui/image.cc new file mode 100644 index 0000000..d821bee --- /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 dggui +{ + +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; +} + +} // dggui:: diff --git a/dggui/image.h b/dggui/image.h new file mode 100644 index 0000000..a6bf43e --- /dev/null +++ b/dggui/image.h @@ -0,0 +1,76 @@ +/* -*- 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 dggui +{ + +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}; +}; + +} // dggui:: diff --git a/dggui/imagecache.cc b/dggui/imagecache.cc new file mode 100644 index 0000000..fa2197b --- /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 dggui +{ + +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); +	} +} + +} // dggui:: diff --git a/dggui/imagecache.h b/dggui/imagecache.h new file mode 100644 index 0000000..15ecaf3 --- /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 dggui +{ + +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; +}; + +} // dggui:: diff --git a/dggui/knob.cc b/dggui/knob.cc new file mode 100644 index 0000000..4f8ead0 --- /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 dggui +{ + +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(); +} + +} // dggui:: diff --git a/dggui/knob.h b/dggui/knob.h new file mode 100644 index 0000000..851c9c6 --- /dev/null +++ b/dggui/knob.h @@ -0,0 +1,89 @@ +/* -*- 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 dggui +{ + +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; +}; + +} // dggui:: diff --git a/dggui/label.cc b/dggui/label.cc new file mode 100644 index 0000000..fc3f60e --- /dev/null +++ b/dggui/label.cc @@ -0,0 +1,97 @@ +/* -*- 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 dggui +{ + +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); +	} +} + +} // dggui:: diff --git a/dggui/label.h b/dggui/label.h new file mode 100644 index 0000000..453bc4f --- /dev/null +++ b/dggui/label.h @@ -0,0 +1,70 @@ +/* -*- 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 dggui +{ + +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; +}; + +} // dggui:: diff --git a/dggui/layout.cc b/dggui/layout.cc new file mode 100644 index 0000000..3b99456 --- /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 dggui +{ + +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); +	} +} + +} // dggui:: diff --git a/dggui/layout.h b/dggui/layout.h new file mode 100644 index 0000000..860ecc2 --- /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 dggui +{ + +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; +}; + +} // dggui:: diff --git a/dggui/led.cc b/dggui/led.cc new file mode 100644 index 0000000..6e1059c --- /dev/null +++ b/dggui/led.cc @@ -0,0 +1,98 @@ +/* -*- 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 dggui +{ + +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); +} + +} // dggui:: diff --git a/dggui/led.h b/dggui/led.h new file mode 100644 index 0000000..7a1f55f --- /dev/null +++ b/dggui/led.h @@ -0,0 +1,55 @@ +/* -*- 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 dggui +{ + +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; +}; + +} // dggui:: diff --git a/dggui/lineedit.cc b/dggui/lineedit.cc new file mode 100644 index 0000000..7e8bf86 --- /dev/null +++ b/dggui/lineedit.cc @@ -0,0 +1,286 @@ +/* -*- 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 dggui +{ + +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); +	} +} + +} // dggui:: diff --git a/dggui/lineedit.h b/dggui/lineedit.h new file mode 100644 index 0000000..c702c47 --- /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 dggui +{ + +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; +}; + +} // dggui:: diff --git a/dggui/listbox.cc b/dggui/listbox.cc new file mode 100644 index 0000000..4513895 --- /dev/null +++ b/dggui/listbox.cc @@ -0,0 +1,106 @@ +/* -*- 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 dggui +{ + +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)); +} + +} // dggui:: diff --git a/dggui/listbox.h b/dggui/listbox.h new file mode 100644 index 0000000..75a4ea0 --- /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 dggui +{ + +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 +}; + +} // dggui:: diff --git a/dggui/listboxbasic.cc b/dggui/listboxbasic.cc new file mode 100644 index 0000000..dd58944 --- /dev/null +++ b/dggui/listboxbasic.cc @@ -0,0 +1,363 @@ +/* -*- 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 dggui +{ + +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); +} + +} // dggui:: diff --git a/dggui/listboxbasic.h b/dggui/listboxbasic.h new file mode 100644 index 0000000..3d6515d --- /dev/null +++ b/dggui/listboxbasic.h @@ -0,0 +1,97 @@ +/* -*- 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 dggui +{ + +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; +}; + +} // dggui:: diff --git a/dggui/listboxthin.cc b/dggui/listboxthin.cc new file mode 100644 index 0000000..b92def3 --- /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 dggui +{ + +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)); +} + +} // dggui:: diff --git a/dggui/listboxthin.h b/dggui/listboxthin.h new file mode 100644 index 0000000..0305a04 --- /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 dggui +{ + +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(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 +}; + +} // dggui:: 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..07c3559 --- /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 dggui +{ + +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; +}; + +} // dggui:: diff --git a/dggui/nativewindow_cocoa.h b/dggui/nativewindow_cocoa.h new file mode 100644 index 0000000..e17d4af --- /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 dggui +{ + +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}; +}; + +} // dggui:: diff --git a/dggui/nativewindow_cocoa.mm b/dggui/nativewindow_cocoa.mm new file mode 100644 index 0000000..3214f57 --- /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; +	dggui::NativeWindowCocoa* native; +} + +- (id) initWithWindow:(NSWindow*)ref +               native:(dggui::NativeWindowCocoa*)_native; +- (void) dealloc; +- (void) windowDidResize; +- (void) windowWillResize; +- (void) windowWillClose; +- (void) unbindNative; +@end + +@implementation DGListener +- (id) initWithWindow:(NSWindow*)ref +               native:(dggui::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<dggui::CloseEvent>(); +	native->pushBackEvent(closeEvent); +} + +- (void) unbindNative +{ +	native = nullptr; +} +@end + +@interface DGView : NSView +{ +	int colorBits; +	int depthBits; + +@private +	dggui::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:(dggui::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<dggui::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<dggui::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<dggui::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<dggui::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 = dggui::MouseButton::left; +		break; +	case 1: +		buttonEvent->button = dggui::MouseButton::right; +		break; +	case 2: +		buttonEvent->button = dggui::MouseButton::middle; +		break; +	default: +		return; +	} +	buttonEvent->direction = dggui::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<dggui::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 = dggui::MouseButton::left; +		break; +	case 1: +		buttonEvent->button = dggui::MouseButton::right; +		break; +	case 2: +		buttonEvent->button = dggui::MouseButton::middle; +		break; +	default: +		return; +	} +	buttonEvent->direction = dggui::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<dggui::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<dggui::KeyEvent>(); + +	switch([event keyCode]) +	{ +	case 123: keyEvent->keycode = dggui::Key::left; break; +	case 124: keyEvent->keycode = dggui::Key::right; break; +	case 126: keyEvent->keycode = dggui::Key::up; break; +	case 125: keyEvent->keycode = dggui::Key::down; break; +	case 117: keyEvent->keycode = dggui::Key::deleteKey; break; +	case 51:  keyEvent->keycode = dggui::Key::backspace; break; +	case 115: keyEvent->keycode = dggui::Key::home; break; +	case 119: keyEvent->keycode = dggui::Key::end; break; +	case 121: keyEvent->keycode = dggui::Key::pageDown; break; +	case 116: keyEvent->keycode = dggui::Key::pageUp; break; +	case 36:  keyEvent->keycode = dggui::Key::enter; break; +	default:  keyEvent->keycode = dggui::Key::unknown; break; +	} + +	if(strlen(str) && keyEvent->keycode == dggui::Key::unknown) +	{ +		keyEvent->keycode = dggui::Key::character; +	} + +	keyEvent->text = str; // TODO: UTF8 decode +	keyEvent->direction = dggui::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<dggui::KeyEvent>(); + +	switch([event keyCode]) +	{ +	case 123: keyEvent->keycode = dggui::Key::left; break; +	case 124: keyEvent->keycode = dggui::Key::right; break; +	case 126: keyEvent->keycode = dggui::Key::up; break; +	case 125: keyEvent->keycode = dggui::Key::down; break; +	case 117: keyEvent->keycode = dggui::Key::deleteKey; break; +	case 51:  keyEvent->keycode = dggui::Key::backspace; break; +	case 115: keyEvent->keycode = dggui::Key::home; break; +	case 119: keyEvent->keycode = dggui::Key::end; break; +	case 121: keyEvent->keycode = dggui::Key::pageDown; break; +	case 116: keyEvent->keycode = dggui::Key::pageUp; break; +	case 36:  keyEvent->keycode = dggui::Key::enter; break; +	default:  keyEvent->keycode = dggui::Key::unknown; break; +	} + +	if(strlen(str) && keyEvent->keycode == dggui::Key::unknown) +	{ +		keyEvent->keycode = dggui::Key::character; +	} + +	keyEvent->text = str; // TODO: UTF8 decode +	keyEvent->direction = dggui::Direction::up; + +	native->pushBackEvent(keyEvent); +	[super keyUp: event]; +} + +- (void) dealloc +{ +	[super dealloc]; +} + +- (void)bindNative:(dggui::NativeWindowCocoa*)_native +{ +	native = _native; +} + +- (void) unbindNative +{ +	native = nullptr; +} +@end + + +namespace dggui +{ + +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<dggui::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); +} + +} // dggui:: diff --git a/dggui/nativewindow_pugl.cc b/dggui/nativewindow_pugl.cc new file mode 100644 index 0000000..68b1ea9 --- /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 dggui +{ + +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); +} + +} // dggui:: diff --git a/dggui/nativewindow_pugl.h b/dggui/nativewindow_pugl.h new file mode 100644 index 0000000..c76b157 --- /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 dggui +{ + +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}; +}; + +} // dggui:: diff --git a/dggui/nativewindow_win32.cc b/dggui/nativewindow_win32.cc new file mode 100644 index 0000000..db785e9 --- /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 dggui +{ + +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 }; +} + +} // dggui:: diff --git a/dggui/nativewindow_win32.h b/dggui/nativewindow_win32.h new file mode 100644 index 0000000..f4748fe --- /dev/null +++ b/dggui/nativewindow_win32.h @@ -0,0 +1,79 @@ +/* -*- 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 dggui +{ + +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}; +}; + +} // dggui:: diff --git a/dggui/nativewindow_x11.cc b/dggui/nativewindow_x11.cc new file mode 100644 index 0000000..04f1b35 --- /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 dggui +{ + +#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); +			} +		} +	} +} + +} // dggui:: diff --git a/dggui/nativewindow_x11.h b/dggui/nativewindow_x11.h new file mode 100644 index 0000000..bed86bd --- /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 dggui +{ + +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; +}; + +} // dggui:: diff --git a/dggui/painter.cc b/dggui/painter.cc new file mode 100644 index 0000000..c326601 --- /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 dggui +{ + +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); +} + +} // dggui:: diff --git a/dggui/painter.h b/dggui/painter.h new file mode 100644 index 0000000..09c490d --- /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 dggui +{ + +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); +	} +} + +} // dggui:: diff --git a/dggui/pixelbuffer.cc b/dggui/pixelbuffer.cc new file mode 100644 index 0000000..531ac22 --- /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 dggui +{ + +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; +} + +} // dggui:: diff --git a/dggui/pixelbuffer.h b/dggui/pixelbuffer.h new file mode 100644 index 0000000..58fec00 --- /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 dggui +{ + +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}; +}; + +} // dggui:: diff --git a/dggui/powerbutton.cc b/dggui/powerbutton.cc new file mode 100644 index 0000000..8a204f2 --- /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 dggui +{ + +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); +	} +} + +} // dggui:: diff --git a/dggui/powerbutton.h b/dggui/powerbutton.h new file mode 100644 index 0000000..a752c53 --- /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 dggui +{ + +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}; +}; + +} // dggui:: diff --git a/dggui/progressbar.cc b/dggui/progressbar.cc new file mode 100644 index 0000000..1f833f9 --- /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 dggui +{ + +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; +	} + +} + +} // dggui:: diff --git a/dggui/progressbar.h b/dggui/progressbar.h new file mode 100644 index 0000000..a67687b --- /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 dggui +{ + +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}; +}; + +} // dggui:: 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..1e4d95e --- /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 dggui +{ + +// 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; +} + +} // dggui:: diff --git a/dggui/resource.h b/dggui/resource.h new file mode 100644 index 0000000..f298287 --- /dev/null +++ b/dggui/resource.h @@ -0,0 +1,52 @@ +/* -*- 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 dggui +{ + +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}; +}; + +} // dggui:: diff --git a/dggui/resource_data.h b/dggui/resource_data.h new file mode 100644 index 0000000..0da3720 --- /dev/null +++ b/dggui/resource_data.h @@ -0,0 +1,34 @@ +/* -*- 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 + +struct rc_data_t +{ +	const char *name; +	unsigned int size; +	const char *data; +}; diff --git a/dggui/scrollbar.cc b/dggui/scrollbar.cc new file mode 100644 index 0000000..e78aab3 --- /dev/null +++ b/dggui/scrollbar.cc @@ -0,0 +1,215 @@ +/* -*- 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 dggui +{ + +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); +} + +} // dggui:: diff --git a/dggui/scrollbar.h b/dggui/scrollbar.h new file mode 100644 index 0000000..9bc4de8 --- /dev/null +++ b/dggui/scrollbar.h @@ -0,0 +1,77 @@ +/* -*- 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 dggui +{ + +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}; +}; + +} // dggui:: diff --git a/dggui/slider.cc b/dggui/slider.cc new file mode 100644 index 0000000..bb42f34 --- /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 dggui +{ + +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; +	} +} + +} // dggui:: diff --git a/dggui/slider.h b/dggui/slider.h new file mode 100644 index 0000000..b249c91 --- /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 dggui +{ + +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); +}; + +} // dggui:: diff --git a/dggui/stackedwidget.cc b/dggui/stackedwidget.cc new file mode 100644 index 0000000..a89dc04 --- /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 dggui +{ + +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); +	} +} + +} // dggui:: diff --git a/dggui/stackedwidget.h b/dggui/stackedwidget.h new file mode 100644 index 0000000..a08065b --- /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 dggui +{ + +//! 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; +}; + +} // dggui:: diff --git a/dggui/tabbutton.cc b/dggui/tabbutton.cc new file mode 100644 index 0000000..a2b0549 --- /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 dggui +{ + +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); +} + +} // dggui:: diff --git a/dggui/tabbutton.h b/dggui/tabbutton.h new file mode 100644 index 0000000..ac9a37c --- /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 dggui +{ + +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"}; +}; + +} // dggui:: diff --git a/dggui/tabwidget.cc b/dggui/tabwidget.cc new file mode 100644 index 0000000..1714472 --- /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 dggui +{ + +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; +} + +} // dggui:: diff --git a/dggui/tabwidget.h b/dggui/tabwidget.h new file mode 100644 index 0000000..750ed59 --- /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 dggui +{ + +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}; +}; + +} // dggui:: diff --git a/dggui/textedit.cc b/dggui/textedit.cc new file mode 100644 index 0000000..f8aeab7 --- /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 dggui +{ + +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(); +} + +} // dggui:: diff --git a/dggui/textedit.h b/dggui/textedit.h new file mode 100644 index 0000000..c07774b --- /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 dggui +{ + +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; +}; + +} // dggui:: diff --git a/dggui/texture.cc b/dggui/texture.cc new file mode 100644 index 0000000..788835f --- /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 dggui +{ + +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; +} + +} // dggui:: diff --git a/dggui/texture.h b/dggui/texture.h new file mode 100644 index 0000000..d75b47a --- /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 dggui +{ + +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}; +}; + +} // dggui:: diff --git a/dggui/texturedbox.cc b/dggui/texturedbox.cc new file mode 100644 index 0000000..c19b8d6 --- /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 dggui +{ + +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; +} + +} // dggui:: diff --git a/dggui/texturedbox.h b/dggui/texturedbox.h new file mode 100644 index 0000000..48febba --- /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 dggui +{ + +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}; +}; + +} // dggui:: diff --git a/dggui/toggle.cc b/dggui/toggle.cc new file mode 100644 index 0000000..e655a65 --- /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 dggui +{ + +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(); +} + +} // dggui:: diff --git a/dggui/toggle.h b/dggui/toggle.h new file mode 100644 index 0000000..0fa844b --- /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 dggui +{ + +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); +}; + +} // dggui:: diff --git a/dggui/tooltip.cc b/dggui/tooltip.cc new file mode 100644 index 0000000..b55b45e --- /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 dggui +{ + +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(); +	} +} + +} // dggui:: diff --git a/dggui/tooltip.h b/dggui/tooltip.h new file mode 100644 index 0000000..1ba1cd8 --- /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 dggui +{ + +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; +}; + +} // dggui:: diff --git a/dggui/uitranslation.cc b/dggui/uitranslation.cc new file mode 100644 index 0000000..c12d646 --- /dev/null +++ b/dggui/uitranslation.cc @@ -0,0 +1,55 @@ +/* -*- 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" + +namespace dggui +{ + +UITranslation::UITranslation() +{ +	auto lang = Translation::getISO639LanguageName(); +	printf("LANG: %s\n", lang.data()); +	std::string res = ":locale/"; +	res += lang + ".mo"; + +	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()); +} + +} // ::dggui +#endif // WITH_NLS diff --git a/dggui/uitranslation.h b/dggui/uitranslation.h new file mode 100644 index 0000000..3258e21 --- /dev/null +++ b/dggui/uitranslation.h @@ -0,0 +1,46 @@ +/* -*- 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 +namespace dggui +{ + +class UITranslation +	: public Translation +{ +public: +	UITranslation(); +	~UITranslation() = default; +}; + +} // ::dggui +#endif diff --git a/dggui/utf8.cc b/dggui/utf8.cc new file mode 100644 index 0000000..c516f52 --- /dev/null +++ b/dggui/utf8.cc @@ -0,0 +1,366 @@ +/* -*- 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" + +namespace dggui +{ + +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; +} + +} // ::dggui diff --git a/dggui/utf8.h b/dggui/utf8.h new file mode 100644 index 0000000..c7cf2ac --- /dev/null +++ b/dggui/utf8.h @@ -0,0 +1,52 @@ +/* -*- 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> + +namespace dggui +{ + +// 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; +}; + +} // ::dggui diff --git a/dggui/verticalline.cc b/dggui/verticalline.cc new file mode 100644 index 0000000..d068b82 --- /dev/null +++ b/dggui/verticalline.cc @@ -0,0 +1,52 @@ +/* -*- 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 dggui +{ + +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()); +} + +} // dggui:: diff --git a/dggui/verticalline.h b/dggui/verticalline.h new file mode 100644 index 0000000..2479d0d --- /dev/null +++ b/dggui/verticalline.h @@ -0,0 +1,50 @@ +/* -*- 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 dggui +{ + +class VerticalLine +	: public Widget +{ +public: +	VerticalLine(Widget* parent); +	virtual ~VerticalLine() = default; + +protected: +	// From Widget: +	virtual void repaintEvent(RepaintEvent* repaintEvent) override; + +private: +	Image vline; +}; + +} // dggui:: diff --git a/dggui/widget.cc b/dggui/widget.cc new file mode 100644 index 0000000..077fa72 --- /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 dggui +{ + +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; +} + +} // dggui:: diff --git a/dggui/widget.h b/dggui/widget.h new file mode 100644 index 0000000..bf391c5 --- /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 dggui +{ + +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}; +}; + +} // dggui:: diff --git a/dggui/window.cc b/dggui/window.cc new file mode 100644 index 0000000..19af2ad --- /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 dggui +{ + +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; +} + +} // dggui:: diff --git a/dggui/window.h b/dggui/window.h new file mode 100644 index 0000000..5f6952f --- /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 dggui +{ + +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; +}; + +} // dggui:: | 
