From cec1d9ea562e3d52c98f1219db5e186943f2f0d6 Mon Sep 17 00:00:00 2001
From: Bent Bisballe Nyeng <deva@aasimon.org>
Date: Sun, 12 Feb 2017 11:07:22 +0100
Subject: Refactor/introduce widget and windiow redraw/dirty mechanism to
 eradicate unnecessary rendering passes during event handling.

---
 plugin/drumgizmo_plugin.cc      |   5 +-
 plugingui/button.cc             |  10 ++--
 plugingui/canvas.h              |   8 ---
 plugingui/checkbox.cc           |  12 ++---
 plugingui/combobox.cc           |   8 +--
 plugingui/eventhandler.cc       |  92 +++++----------------------------
 plugingui/eventhandler.h        |   4 --
 plugingui/knob.cc               |   2 +-
 plugingui/label.cc              |   2 +-
 plugingui/layout.h              |   8 +--
 plugingui/led.cc                |   2 +-
 plugingui/lineedit.cc           |   6 +--
 plugingui/listboxbasic.cc       |  14 ++---
 plugingui/mainwindow.cc         |   6 +--
 plugingui/nativewindow.h        |   4 --
 plugingui/nativewindow_win32.cc |   5 +-
 plugingui/nativewindow_win32.h  |   1 -
 plugingui/nativewindow_x11.cc   |  23 ++-------
 plugingui/nativewindow_x11.h    |   1 -
 plugingui/painter.cc            |  19 +++----
 plugingui/painter.h             |  14 +++--
 plugingui/pixelbuffer.cc        |   2 +-
 plugingui/pixelbuffer.h         |   2 +-
 plugingui/progressbar.cc        |   6 +--
 plugingui/scrollbar.cc          |   6 +--
 plugingui/slider.cc             |   8 +--
 plugingui/textedit.cc           |   4 +-
 plugingui/widget.cc             |  97 ++++++++++++++++-------------------
 plugingui/widget.h              |  34 ++++++------
 plugingui/window.cc             | 111 +++++++++++++++-------------------------
 plugingui/window.h              |  22 ++++----
 31 files changed, 196 insertions(+), 342 deletions(-)

diff --git a/plugin/drumgizmo_plugin.cc b/plugin/drumgizmo_plugin.cc
index 091492f..3db72c1 100644
--- a/plugin/drumgizmo_plugin.cc
+++ b/plugin/drumgizmo_plugin.cc
@@ -185,12 +185,11 @@ public:
 	{
 	}
 
-	GUI::PixelBufferAlpha& GetPixelBuffer()
+	// From Canvas:
+	GUI::PixelBufferAlpha& GetPixelBuffer() override
 	{
 		return pixbuf;
 	}
-	void beginPaint() {}
-	void endPaint() {}
 
 private:
 	InlinePixelBufferAlpha pixbuf;
diff --git a/plugingui/button.cc b/plugingui/button.cc
index 29d3deb..fb25390 100644
--- a/plugingui/button.cc
+++ b/plugingui/button.cc
@@ -57,14 +57,14 @@ void Button::buttonEvent(ButtonEvent* buttonEvent)
 		draw_state = down;
 		button_state = down;
 		in_button = true;
-		repaintEvent(nullptr);
+		redraw();
 	}
 
 	if(buttonEvent->direction == Direction::up)
 	{
 		draw_state = up;
 		button_state = up;
-		repaintEvent(nullptr);
+		redraw();
 		if(in_button)
 		{
 			clicked();
@@ -110,7 +110,7 @@ void Button::repaintEvent(RepaintEvent* repaintEvent)
 void Button::setText(const std::string& text)
 {
 	this->text = text;
-	repaintEvent(nullptr);
+	redraw();
 }
 
 void Button::mouseLeaveEvent()
@@ -119,7 +119,7 @@ void Button::mouseLeaveEvent()
 	if(button_state == down)
 	{
 		draw_state = up;
-		repaintEvent(nullptr);
+		redraw();
 	}
 }
 
@@ -129,7 +129,7 @@ void Button::mouseEnterEvent()
 	if(button_state == down)
 	{
 		draw_state = down;
-		repaintEvent(nullptr);
+		redraw();
 	}
 }
 
diff --git a/plugingui/canvas.h b/plugingui/canvas.h
index cafe483..2326f0e 100644
--- a/plugingui/canvas.h
+++ b/plugingui/canvas.h
@@ -39,14 +39,6 @@ public:
 
 	//! @returns a reference to the pixel buffer.
 	virtual PixelBufferAlpha& GetPixelBuffer() = 0;
-
-	//! Signal the beginning of a paint operation.
-	virtual void beginPaint() = 0;
-
-	//! Signal the ending of a paint operation
-	//! This might trigger a redraw operation.
-	virtual void endPaint() = 0;
-
 };
 
 } // GUI::
diff --git a/plugingui/checkbox.cc b/plugingui/checkbox.cc
index bada623..2a17635 100644
--- a/plugingui/checkbox.cc
+++ b/plugingui/checkbox.cc
@@ -62,13 +62,13 @@ void CheckBox::buttonEvent(ButtonEvent* buttonEvent)
 		middle = true;
 	}
 
-	repaintEvent(nullptr);
+	redraw();
 }
 
 void CheckBox::setText(std::string text)
 {
 	_text = text;
-	repaintEvent(nullptr);
+	redraw();
 }
 
 void CheckBox::keyEvent(KeyEvent* keyEvent)
@@ -85,7 +85,7 @@ void CheckBox::keyEvent(KeyEvent* keyEvent)
 			middle = true;
 		}
 
-		repaintEvent(nullptr);
+		redraw();
 	}
 }
 
@@ -129,7 +129,7 @@ void CheckBox::mouseLeaveEvent()
 	if(buttonDown)
 	{
 		middle = false;
-		repaintEvent(nullptr);
+		redraw();
 	}
 }
 
@@ -139,7 +139,7 @@ void CheckBox::mouseEnterEvent()
 	if(buttonDown)
 	{
 		middle = true;
-		repaintEvent(nullptr);
+		redraw();
 	}
 }
 
@@ -152,7 +152,7 @@ void CheckBox::internalSetChecked(bool checked)
 
 	state = checked;
 	stateChangedNotifier(state);
-	repaintEvent(nullptr);
+	redraw();
 }
 
 } // GUI::
diff --git a/plugingui/combobox.cc b/plugingui/combobox.cc
index ffbde88..82fc6d1 100644
--- a/plugingui/combobox.cc
+++ b/plugingui/combobox.cc
@@ -65,13 +65,13 @@ void ComboBox::addItem(std::string name, std::string value)
 void ComboBox::clear()
 {
 	listbox.clear();
-	repaintEvent(nullptr);
+	redraw();
 }
 
 bool ComboBox::selectItem(int index)
 {
 	listbox.selectItem(index);
-	repaintEvent(nullptr);
+	redraw();
 	return true;
 }
 
@@ -143,7 +143,7 @@ void ComboBox::scrollEvent(ScrollEvent* scrollEvent)
 	{
 		scroll_offset = (items.size() - 1);
 	}
-	repaintEvent(nullptr);
+	redraw();
 	*/
 }
 
@@ -203,7 +203,7 @@ void ComboBox::keyEvent(KeyEvent* keyEvent)
 		break;
 	}
 
-	repaintEvent(nullptr);
+	redraw();
 	*/
 }
 
diff --git a/plugingui/eventhandler.cc b/plugingui/eventhandler.cc
index aded993..2cdb6b1 100644
--- a/plugingui/eventhandler.cc
+++ b/plugingui/eventhandler.cc
@@ -56,21 +56,8 @@ std::shared_ptr<Event> EventHandler::getNextEvent()
 	return event;
 }
 
-std::shared_ptr<Event> EventHandler::peekNextEvent()
-{
-	if(events.empty())
-	{
-		return nullptr;
-	}
-
-	auto event = events.front();
-	return event;
-}
-
 void EventHandler::processEvents()
 {
-	Painter p(window); // Make sure we only redraw buffer one time.
-
 	events = nativeWindow.getEvents();
 
 	while(hasEvent())
@@ -96,26 +83,6 @@ void EventHandler::processEvents()
 
 		case EventType::resize:
 			{
-				while(true)
-				{
-					if(!hasEvent())
-					{
-						break;
-					}
-
-					auto peekEvent = peekNextEvent();
-					if(!peekEvent)
-					{
-						break;
-					}
-					if(peekEvent->type() != EventType::resize)
-					{
-						break;
-					}
-
-					event = getNextEvent();
-				}
-
 				auto resizeEvent = static_cast<ResizeEvent*>(event.get());
 				if((resizeEvent->width != window.width()) ||
 				   (resizeEvent->height != window.height()))
@@ -134,16 +101,6 @@ void EventHandler::processEvents()
 						break;
 					}
 
-					auto peekEvent = peekNextEvent();
-					if(!peekEvent)
-					{
-						break;
-					}
-					if(peekEvent->type() != EventType::mouseMove)
-					{
-						break;
-					}
-
 					event = getNextEvent();
 				}
 
@@ -171,8 +128,8 @@ void EventHandler::processEvents()
 				if(window.buttonDownFocus())
 				{
 					auto widget = window.buttonDownFocus();
-					moveEvent->x -= widget->windowX();
-					moveEvent->y -= widget->windowY();
+					moveEvent->x -= widget->translateToWindowX();
+					moveEvent->y -= widget->translateToWindowY();
 
 					window.buttonDownFocus()->mouseMoveEvent(moveEvent);
 					break;
@@ -180,8 +137,8 @@ void EventHandler::processEvents()
 
 				if(widget)
 				{
-					moveEvent->x -= widget->windowX();
-					moveEvent->y -= widget->windowY();
+					moveEvent->x -= widget->translateToWindowX();
+					moveEvent->y -= widget->translateToWindowY();
 					widget->mouseMoveEvent(moveEvent);
 				}
 			}
@@ -206,8 +163,8 @@ void EventHandler::processEvents()
 					if(buttonEvent->direction == Direction::up)
 					{
 						auto widget = window.buttonDownFocus();
-						buttonEvent->x -= widget->windowX();
-						buttonEvent->y -= widget->windowY();
+						buttonEvent->x -= widget->translateToWindowX();
+						buttonEvent->y -= widget->translateToWindowY();
 
 						widget->buttonEvent(buttonEvent);
 						window.setButtonDownFocus(nullptr);
@@ -217,8 +174,8 @@ void EventHandler::processEvents()
 
 				if(widget)
 				{
-					buttonEvent->x -= widget->windowX();
-					buttonEvent->y -= widget->windowY();
+					buttonEvent->x -= widget->translateToWindowX();
+					buttonEvent->y -= widget->translateToWindowY();
 
 					widget->buttonEvent(buttonEvent);
 
@@ -238,38 +195,13 @@ void EventHandler::processEvents()
 
 		case EventType::scroll:
 			{
-				int delta = 0;
-				while(true)
-				{
-					if(!hasEvent())
-					{
-						break;
-					}
-
-					auto peekEvent = peekNextEvent();
-					if(!peekEvent)
-					{
-						break;
-					}
-					if(peekEvent->type() != EventType::scroll)
-					{
-						break;
-					}
-
-					auto scrollEvent = static_cast<ScrollEvent*>(event.get());
-					delta += scrollEvent->delta;
-
-					event = getNextEvent();
-				}
-
 				auto scrollEvent = static_cast<ScrollEvent*>(event.get());
-				scrollEvent->delta += delta;
 
 				auto widget = window.find(scrollEvent->x, scrollEvent->y);
 				if(widget)
 				{
-					scrollEvent->x -= widget->windowX();
-					scrollEvent->y -= widget->windowY();
+					scrollEvent->x -= widget->translateToWindowX();
+					scrollEvent->y -= widget->translateToWindowY();
 
 					widget->scrollEvent(scrollEvent);
 				}
@@ -294,6 +226,10 @@ void EventHandler::processEvents()
 			break;
 		}
 	}
+
+	// Probe window and children to readrw as needed.
+	// NOTE: This method will invoke native->redraw() if a redraw is needed.
+	window.updateBuffer();
 }
 
 } // GUI::
diff --git a/plugingui/eventhandler.h b/plugingui/eventhandler.h
index 2a1b657..7e9966c 100644
--- a/plugingui/eventhandler.h
+++ b/plugingui/eventhandler.h
@@ -53,10 +53,6 @@ public:
 	//! \return A pointer to the event or nullptr if there are none.
 	std::shared_ptr<Event> getNextEvent();
 
-	//! \brief Get a single event from the event queue without popping it.
-	//! \return A pointer to the event or nullptr if there are none.
-	std::shared_ptr<Event> peekNextEvent();
-
 	Notifier<> closeNotifier;
 
 private:
diff --git a/plugingui/knob.cc b/plugingui/knob.cc
index 87779ec..0655a68 100644
--- a/plugingui/knob.cc
+++ b/plugingui/knob.cc
@@ -203,7 +203,7 @@ void Knob::internalSetValue(float value)
 
 	currentValue = value;
 	valueChangedNotifier(currentValue);
-	repaintEvent(nullptr);
+	redraw();
 }
 
 } // GUI::
diff --git a/plugingui/label.cc b/plugingui/label.cc
index 2ff4f36..781dbca 100644
--- a/plugingui/label.cc
+++ b/plugingui/label.cc
@@ -39,7 +39,7 @@ Label::Label(Widget *parent)
 void Label::setText(const std::string& text)
 {
 	_text = text;
-	repaintEvent(nullptr);
+	redraw();
 }
 
 void Label::setAlignment(TextAlignment alignment)
diff --git a/plugingui/layout.h b/plugingui/layout.h
index bd64fdd..4d20eb6 100644
--- a/plugingui/layout.h
+++ b/plugingui/layout.h
@@ -46,10 +46,10 @@ public:
 
 	virtual void resize(std::size_t width, std::size_t height) = 0;
 	virtual void move(int x, int y) = 0;
-	virtual int x() = 0;
-	virtual int y() = 0;
-	virtual std::size_t width() = 0;
-	virtual std::size_t height() = 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;
diff --git a/plugingui/led.cc b/plugingui/led.cc
index 03ea661..f77e31a 100644
--- a/plugingui/led.cc
+++ b/plugingui/led.cc
@@ -41,7 +41,7 @@ void LED::setState(state_t state)
 	if(this->state != state)
 	{
 		this->state = state;
-		repaintEvent(nullptr);
+		redraw();
 	}
 }
 
diff --git a/plugingui/lineedit.cc b/plugingui/lineedit.cc
index c213041..7a8bb53 100644
--- a/plugingui/lineedit.cc
+++ b/plugingui/lineedit.cc
@@ -61,7 +61,7 @@ void LineEdit::setText(const std::string& text)
 	visibleText = _text;
 	offsetPos = 0;
 
-	repaintEvent(nullptr);
+	redraw();
 	textChanged();
 }
 
@@ -94,7 +94,7 @@ void LineEdit::buttonEvent(ButtonEvent *buttonEvent)
 				break;
 			}
 		}
-		repaintEvent(nullptr);
+		redraw();
 	}
 }
 
@@ -189,7 +189,7 @@ void LineEdit::keyEvent(KeyEvent *keyEvent)
 			break;
 		}
 
-		repaintEvent(nullptr);
+		redraw();
 	}
 
 	if(change)
diff --git a/plugingui/listboxbasic.cc b/plugingui/listboxbasic.cc
index 730880a..642d03f 100644
--- a/plugingui/listboxbasic.cc
+++ b/plugingui/listboxbasic.cc
@@ -88,7 +88,7 @@ void ListBoxBasic::addItems(const std::vector<ListBoxBasic::Item>& newItems)
 	int numitems = height() / (font.textHeight() + padding);
 	scroll.setRange(numitems);
 	scroll.setMaximum(items.size());
-	repaintEvent(nullptr);
+	redraw();
 }
 
 void ListBoxBasic::clear()
@@ -97,7 +97,7 @@ void ListBoxBasic::clear()
 	setSelection(-1);
 	marked = -1;
 	scroll.setValue(0);
-	repaintEvent(nullptr);
+	redraw();
 }
 
 bool ListBoxBasic::selectItem(int index)
@@ -108,7 +108,7 @@ bool ListBoxBasic::selectItem(int index)
 	}
 
 	setSelection(index);
-	repaintEvent(nullptr);
+	redraw();
 
 	return true;
 }
@@ -140,7 +140,7 @@ void ListBoxBasic::clearSelectedValue()
 
 void ListBoxBasic::onScrollBarValueChange(int value)
 {
-	repaintEvent(nullptr);
+	redraw();
 }
 
 void ListBoxBasic::repaintEvent(RepaintEvent* repaintEvent)
@@ -277,7 +277,7 @@ void ListBoxBasic::keyEvent(KeyEvent* keyEvent)
 		break;
 	}
 
-	repaintEvent(nullptr);
+	redraw();
 }
 
 void ListBoxBasic::buttonEvent(ButtonEvent* buttonEvent)
@@ -329,7 +329,7 @@ void ListBoxBasic::buttonEvent(ButtonEvent* buttonEvent)
 			}
 		}
 
-		repaintEvent(nullptr);
+		redraw();
 	}
 
 	if(buttonEvent->direction != Direction::up)
@@ -346,7 +346,7 @@ void ListBoxBasic::buttonEvent(ButtonEvent* buttonEvent)
 			}
 		}
 
-		repaintEvent(nullptr);
+		redraw();
 	}
 
 	if(buttonEvent->doubleClick)
diff --git a/plugingui/mainwindow.cc b/plugingui/mainwindow.cc
index 3a1727f..3b951f5 100644
--- a/plugingui/mainwindow.cc
+++ b/plugingui/mainwindow.cc
@@ -50,13 +50,9 @@ bool MainWindow::processEvents()
 //		return running;
 //	}
 
+	settings_notifier.evaluate();
 	eventHandler()->processEvents();
 
-	{
-		Painter p(*this);
-		settings_notifier.evaluate();
-	}
-
 	if(closing)
 	{
 		closeNotifier();
diff --git a/plugingui/nativewindow.h b/plugingui/nativewindow.h
index e041994..7b7fd39 100644
--- a/plugingui/nativewindow.h
+++ b/plugingui/nativewindow.h
@@ -69,10 +69,6 @@ public:
 	//! Sets the window caption in the title bar (if it has one).
 	virtual void setCaption(const std::string &caption) = 0;
 
-	//! Recreate a window render buffer based on the internal buffer.
-	//! This need to be called whenever the internal buffer size has changed.
-	virtual void handleBuffer() = 0;
-
 	//! Draw the internal rendering buffer to the window buffer.
 	virtual void redraw() = 0;
 
diff --git a/plugingui/nativewindow_win32.cc b/plugingui/nativewindow_win32.cc
index 23c9012..669ec13 100644
--- a/plugingui/nativewindow_win32.cc
+++ b/plugingui/nativewindow_win32.cc
@@ -379,10 +379,6 @@ void NativeWindowWin32::show()
 	ShowWindow(m_hwnd, SW_SHOW);
 }
 
-void NativeWindowWin32::handleBuffer()
-{
-}
-
 void NativeWindowWin32::hide()
 {
 	ShowWindow(m_hwnd, SW_HIDE);
@@ -390,6 +386,7 @@ void NativeWindowWin32::hide()
 
 void NativeWindowWin32::redraw()
 {
+	// Send WM_PAINT message. Buffer transfering is handled in MessageHandler.
 	if(parent_window == nullptr)
 	{
 		RedrawWindow(m_hwnd, nullptr, nullptr, RDW_ERASE|RDW_INVALIDATE);
diff --git a/plugingui/nativewindow_win32.h b/plugingui/nativewindow_win32.h
index b8e1c89..69324e3 100644
--- a/plugingui/nativewindow_win32.h
+++ b/plugingui/nativewindow_win32.h
@@ -52,7 +52,6 @@ public:
 	void show() override;
 	void setCaption(const std::string &caption) override;
 	void hide() override;
-	void handleBuffer() override;
 	void redraw() override;
 	void grabMouse(bool grab) override;
 	EventQueue getEvents() override;
diff --git a/plugingui/nativewindow_x11.cc b/plugingui/nativewindow_x11.cc
index bf18ffa..80c0e81 100644
--- a/plugingui/nativewindow_x11.cc
+++ b/plugingui/nativewindow_x11.cc
@@ -223,11 +223,6 @@ void NativeWindowX11::hide()
 	XUnmapWindow(display, xwindow);
 }
 
-void NativeWindowX11::handleBuffer()
-{
-	updateImageFromBuffer();
-}
-
 void NativeWindowX11::redraw()
 {
 	if(display == nullptr)
@@ -235,12 +230,7 @@ void NativeWindowX11::redraw()
 		return;
 	}
 
-	if(!image)
-	{
-		window.updateBuffer();
-		handleBuffer();
-	}
-
+	updateImageFromBuffer();
 	XShmPutImage(display, xwindow, gc, image, 0, 0, 0, 0,
 	             std::min(image->width, (int)window.width()),
 	             std::min(image->height, (int)window.height()), false);
@@ -286,7 +276,6 @@ void NativeWindowX11::translateXMessage(XEvent& xevent)
 		//DEBUG(x11, "MotionNotify");
 		{
 			auto mouseMoveEvent = std::make_shared<MouseMoveEvent>();
-			//mouseMoveEvent->window_id = xevent.xmotion.window;
 			mouseMoveEvent->x = xevent.xmotion.x;
 			mouseMoveEvent->y = xevent.xmotion.y;
 			event_queue.push_back(mouseMoveEvent);
@@ -298,7 +287,6 @@ void NativeWindowX11::translateXMessage(XEvent& xevent)
 		if(xevent.xexpose.count == 0)
 		{
 			auto repaintEvent = std::make_shared<RepaintEvent>();
-			//repaintEvent->window_id = xevent.xexpose.window;
 			repaintEvent->x = xevent.xexpose.x;
 			repaintEvent->y = xevent.xexpose.y;
 			repaintEvent->width = xevent.xexpose.width;
@@ -314,17 +302,15 @@ void NativeWindowX11::translateXMessage(XEvent& xevent)
 			   (window.height() != (std::size_t)xevent.xconfigure.height))
 			{
 				auto resizeEvent = std::make_shared<ResizeEvent>();
-				//resizeEvent->window_id = xevent.xconfigure.window;
 				resizeEvent->width = xevent.xconfigure.width;
 				resizeEvent->height = xevent.xconfigure.height;
 				event_queue.push_back(resizeEvent);
 			}
 
-			if((window.windowX() != (std::size_t)xevent.xconfigure.x) ||
-			   (window.windowY() != (std::size_t)xevent.xconfigure.y))
+			if((window.x() != xevent.xconfigure.x) ||
+			   (window.y() != xevent.xconfigure.y))
 			{
 				auto moveEvent = std::make_shared<MoveEvent>();
-				//moveEvent->window_id = xevent.xconfigure.window;
 				moveEvent->x = xevent.xconfigure.x;
 				moveEvent->y = xevent.xconfigure.y;
 				event_queue.push_back(moveEvent);
@@ -340,7 +326,6 @@ void NativeWindowX11::translateXMessage(XEvent& xevent)
 			{
 				int scroll = 1;
 				auto scrollEvent = std::make_shared<ScrollEvent>();
-				//scrollEvent->window_id = xevent.xbutton.window;
 				scrollEvent->x = xevent.xbutton.x;
 				scrollEvent->y = xevent.xbutton.y;
 				scrollEvent->delta = scroll * ((xevent.xbutton.button == 4) ? -1 : 1);
@@ -349,7 +334,6 @@ void NativeWindowX11::translateXMessage(XEvent& xevent)
 			else
 			{
 				auto buttonEvent = std::make_shared<ButtonEvent>();
-				//buttonEvent->window_id = xevent.xbutton.window;
 				buttonEvent->x = xevent.xbutton.x;
 				buttonEvent->y = xevent.xbutton.y;
 				switch(xevent.xbutton.button) {
@@ -400,7 +384,6 @@ void NativeWindowX11::translateXMessage(XEvent& xevent)
 		//DEBUG(x11, "KeyPress");
 		{
 			auto keyEvent = std::make_shared<KeyEvent>();
-			//keyEvent->window_id = xevent.xkey.window;
 
 			switch(xevent.xkey.keycode) {
 			case 113: keyEvent->keycode = Key::left; break;
diff --git a/plugingui/nativewindow_x11.h b/plugingui/nativewindow_x11.h
index a026ac8..f98f151 100644
--- a/plugingui/nativewindow_x11.h
+++ b/plugingui/nativewindow_x11.h
@@ -54,7 +54,6 @@ public:
 	void show() override;
 	void hide() override;
 	void setCaption(const std::string &caption) override;
-	void handleBuffer() override;
 	void redraw() override;
 	void grabMouse(bool grab) override;
 	EventQueue getEvents() override;
diff --git a/plugingui/painter.cc b/plugingui/painter.cc
index 3e6fa48..4e7e8ba 100644
--- a/plugingui/painter.cc
+++ b/plugingui/painter.cc
@@ -29,6 +29,12 @@
 #include <cmath>
 #include <cassert>
 
+#include "pixelbuffer.h"
+#include "font.h"
+#include "drawable.h"
+#include "image.h"
+#include "canvas.h"
+
 namespace GUI
 {
 
@@ -36,14 +42,11 @@ Painter::Painter(Canvas& canvas)
 	: canvas(canvas)
 	, pixbuf(canvas.GetPixelBuffer())
 {
-	canvas.beginPaint();
 	colour = Colour(0.0f, 0.0f, 0.0f, 0.5f);
 }
 
 Painter::~Painter()
 {
-	canvas.endPaint();
-	flush();
 }
 
 void Painter::setColour(const Colour& colour)
@@ -161,7 +164,7 @@ void Painter::drawRectangle(int x1, int y1, int x2, int y2)
 
 void Painter::drawFilledRectangle(int x1, int y1, int x2, int y2)
 {
-	for(int y = y1; y < y2; ++y)
+	for(int y = y1; y <= y2; ++y)
 	{
 		drawLine(x1, y, x2, y);
 	}
@@ -543,12 +546,4 @@ void Painter::drawBar(int x, int y, const Bar& bar, int width, int height)
 	                   bar.right->width(), height);
 }
 
-void Painter::flush()
-{
-#ifdef X11
-	// Send the "DrawLine" request to the server
-	//XFlush(gctx->display);
-#endif/*X11*/
-}
-
 } // GUI::
diff --git a/plugingui/painter.h b/plugingui/painter.h
index 7e1fede..3f3c2bb 100644
--- a/plugingui/painter.h
+++ b/plugingui/painter.h
@@ -28,25 +28,23 @@
 
 #include <string>
 
-#include "widget.h"
 #include "colour.h"
-#include "pixelbuffer.h"
-#include "font.h"
-#include "drawable.h"
-#include "texture.h"
-#include "image.h"
 
 namespace GUI
 {
 
+class PixelBufferAlpha;
+class Font;
+class Drawable;
+class Image;
+class Canvas;
+
 class Painter
 {
 public:
 	Painter(Canvas& canvas);
 	~Painter();
 
-	void flush();
-
 	void setColour(const Colour& colour);
 
 	void drawLine(int x1, int y1, int x2, int y2);
diff --git a/plugingui/pixelbuffer.cc b/plugingui/pixelbuffer.cc
index 5d032b4..15270af 100644
--- a/plugingui/pixelbuffer.cc
+++ b/plugingui/pixelbuffer.cc
@@ -156,7 +156,7 @@ void PixelBufferAlpha::pixel(size_t x, size_t y,
                              unsigned char* red,
                              unsigned char* green,
                              unsigned char* blue,
-                             unsigned char* alpha)
+                             unsigned char* alpha) const
 {
 	assert(x < width);
 	assert(y < height);
diff --git a/plugingui/pixelbuffer.h b/plugingui/pixelbuffer.h
index 762aaa6..b9096c9 100644
--- a/plugingui/pixelbuffer.h
+++ b/plugingui/pixelbuffer.h
@@ -76,7 +76,7 @@ public:
 	           unsigned char* red,
 	           unsigned char* green,
 	           unsigned char* blue,
-	           unsigned char* alpha);
+	           unsigned char* alpha) const;
 
 	bool managed{false};
 	unsigned char* buf{nullptr};
diff --git a/plugingui/progressbar.cc b/plugingui/progressbar.cc
index 25e735f..c7550c9 100644
--- a/plugingui/progressbar.cc
+++ b/plugingui/progressbar.cc
@@ -43,7 +43,7 @@ void ProgressBar::setState(ProgressBarState state)
 	if(this->state != state)
 	{
 		this->state = state;
-		repaintEvent(nullptr);
+		redraw();
 	}
 }
 
@@ -52,7 +52,7 @@ void ProgressBar::setTotal(std::size_t total)
 	if(this->total != total)
 	{
 		this->total = total;
-		repaintEvent(nullptr);
+		redraw();
 	}
 }
 
@@ -61,7 +61,7 @@ void ProgressBar::setValue(std::size_t value)
 	if(this->value != value)
 	{
 		this->value = value;
-		repaintEvent(nullptr);
+		redraw();
 	}
 }
 
diff --git a/plugingui/scrollbar.cc b/plugingui/scrollbar.cc
index ac5be15..32db4a4 100644
--- a/plugingui/scrollbar.cc
+++ b/plugingui/scrollbar.cc
@@ -41,7 +41,7 @@ void ScrollBar::setRange(int r)
 {
 	rangeValue = r;
 	setValue(value());
-	repaintEvent(nullptr);
+	redraw();
 }
 
 int ScrollBar::range()
@@ -57,7 +57,7 @@ void ScrollBar::setMaximum(int m)
 		rangeValue = maxValue;
 	}
 	setValue(value());
-	repaintEvent(nullptr);
+	redraw();
 }
 
 int ScrollBar::maximum()
@@ -91,7 +91,7 @@ void ScrollBar::setValue(int value)
 
 	valueChangeNotifier(value);
 
-	repaintEvent(nullptr);
+	redraw();
 }
 
 int ScrollBar::value()
diff --git a/plugingui/slider.cc b/plugingui/slider.cc
index 7405a69..039123e 100644
--- a/plugingui/slider.cc
+++ b/plugingui/slider.cc
@@ -46,7 +46,7 @@ Slider::Slider(Widget *parent)
 void Slider::setValue(float newValue)
 {
 	currentValue = newValue;
-	repaintEvent(nullptr);
+	redraw();
 	clickNotifier();
 }
 
@@ -120,7 +120,7 @@ void Slider::buttonEvent(ButtonEvent* buttonEvent)
 			currentValue = 1;
 		}
 
-		repaintEvent(nullptr);
+		redraw();
 		clickNotifier();
 	}
 
@@ -139,7 +139,7 @@ void Slider::buttonEvent(ButtonEvent* buttonEvent)
 			currentValue = 1;
 		}
 
-		repaintEvent(nullptr);
+		redraw();
 		clickNotifier();
 	}
 }
@@ -160,7 +160,7 @@ void Slider::mouseMoveEvent(MouseMoveEvent* mouseMoveEvent)
 			currentValue = 1;
 		}
 
-		repaintEvent(nullptr);
+		redraw();
 		clickNotifier();
 	}
 }
diff --git a/plugingui/textedit.cc b/plugingui/textedit.cc
index 6274d37..4e94566 100644
--- a/plugingui/textedit.cc
+++ b/plugingui/textedit.cc
@@ -83,7 +83,7 @@ void TextEdit::setText(const std::string& text)
 	scroll.setRange(ran);
 	scroll.setMaximum(preprocessedtext.size());
 
-	repaintEvent(nullptr);
+	redraw();
 
 	textChangedNotifier();
 }
@@ -190,7 +190,7 @@ void TextEdit::repaintEvent(RepaintEvent* repaintEvent)
 void TextEdit::scrolled(int value)
 {
 	(void)value;
-	repaintEvent(nullptr);
+	redraw();
 }
 
 } // GUI::
diff --git a/plugingui/widget.cc b/plugingui/widget.cc
index 9b8b173..239c233 100644
--- a/plugingui/widget.cc
+++ b/plugingui/widget.cc
@@ -31,7 +31,8 @@
 #include "painter.h"
 #include "window.h"
 
-namespace GUI {
+namespace GUI
+{
 
 Widget::Widget(Widget* parent)
 	: parent(parent)
@@ -67,15 +68,21 @@ void Widget::setVisible(bool visible)
 
 	if(visible)
 	{
-		repaintEvent(nullptr);
+		redraw();
 	}
 }
 
-bool Widget::visible()
+bool Widget::visible() const
 {
 	return _visible;
 }
 
+void Widget::redraw()
+{
+	dirty = true;
+	window()->needsRedraw();
+}
+
 void Widget::addChild(Widget* widget)
 {
 	children.push_back(widget);
@@ -124,33 +131,39 @@ void Widget::resize(std::size_t width, std::size_t height)
 	_width = width;
 	_height = height;
 	pixbuf.realloc(width, height);
-	repaintEvent(nullptr);
+	redraw();
 	sizeChangeNotifier(width, height);
 }
 
 void Widget::move(int x, int y)
 {
+	if((_x == x) &&
+	   (_y == y))
+	{
+		return;
+	}
+
 	_x = x;
 	_y = y;
 	positionChangeNotifier(x, y);
 }
 
-int Widget::x()
+int Widget::x() const
 {
 	return _x;
 }
 
-int Widget::y()
+int Widget::y() const
 {
 	return _y;
 }
 
-std::size_t Widget::width()
+std::size_t Widget::width() const
 {
 	return _width;
 }
 
-std::size_t Widget::height()
+std::size_t Widget::height() const
 {
 	return _height;
 }
@@ -160,44 +173,6 @@ PixelBufferAlpha& Widget::GetPixelBuffer()
 	return pixbuf;
 }
 
-void Widget::beginPaint()
-{
-	if(_window)
-	{
-		_window->beginPaint();
-	}
-}
-
-void Widget::endPaint()
-{
-	if(_window)
-	{
-		_window->endPaint();
-	}
-}
-
-size_t Widget::windowX()
-{
-	size_t window_x = x();
-	if(parent)
-	{
-		window_x += parent->windowX();
-	}
-
-	return window_x;
-}
-
-size_t Widget::windowY()
-{
-	size_t window_y = y();
-	if(parent)
-	{
-		window_y += parent->windowY();
-	}
-
-	return window_y;
-}
-
 ImageCache& Widget::getImageCache()
 {
 	assert(parent);
@@ -231,8 +206,14 @@ std::vector<PixelBufferAlpha*> Widget::getPixelBuffers()
 {
 	std::vector<PixelBufferAlpha*> pixelBuffers;
 
-	pixbuf.x = windowX();
-	pixbuf.y = windowY();
+	pixbuf.x = translateToWindowX();
+	pixbuf.y = translateToWindowY();
+
+	if(dirty)
+	{
+		repaintEvent(nullptr);
+		dirty = false;
+	}
 
 	pixelBuffers.push_back(&pixbuf);
 
@@ -254,16 +235,26 @@ bool Widget::hasKeyboardFocus()
 	return window()->keyboardFocus() == this;
 }
 
-void Widget::repaintChildren(RepaintEvent* repaintEvent)
+std::size_t Widget::translateToWindowX()
 {
-	Painter p(*this); // make sure pixbuf refcount is incremented.
+	size_t window_x = x();
+	if(parent)
+	{
+		window_x += parent->translateToWindowX();
+	}
 
-	this->repaintEvent(repaintEvent);
+	return window_x;
+}
 
-	for(auto child : children)
+std::size_t Widget::translateToWindowY()
+{
+	size_t window_y = y();
+	if(parent)
 	{
-		child->repaintChildren(repaintEvent);
+		window_y += parent->translateToWindowY();
 	}
+
+	return window_y;
 }
 
 } // GUI::
diff --git a/plugingui/widget.h b/plugingui/widget.h
index 4ea79e9..0485c32 100644
--- a/plugingui/widget.h
+++ b/plugingui/widget.h
@@ -52,25 +52,22 @@ public:
 
 	virtual void show();
 	virtual void hide();
+	void setVisible(bool visible);
+	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() override;
-	virtual int y() override;
-	virtual std::size_t width() override;
-	virtual std::size_t height() 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;
 
 	// From Canvas
 	PixelBufferAlpha& GetPixelBuffer() override;
-	void beginPaint() override;
-	void endPaint() override;
-
-	//! Translate x-coordinate from parent-space to window-space.
-	virtual size_t windowX();
-
-	//! Translate y-coordinate from parent-space to window-space.
-	virtual size_t windowY();
 
 	virtual bool isFocusable() { return false; }
 	virtual bool catchMouse() { return false; }
@@ -97,14 +94,17 @@ public:
 
 	bool hasKeyboardFocus();
 
-	bool visible();
-	void setVisible(bool visible);
-
 	Notifier<std::size_t, std::size_t> sizeChangeNotifier; // (width, height)
 	Notifier<int, int> positionChangeNotifier; // (x, y)
 
 protected:
-	void repaintChildren(RepaintEvent* repaintEvent);
+	friend class EventHandler;
+
+	//! 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();
 
 	PixelBufferAlpha pixbuf{0,0};
 
@@ -119,6 +119,8 @@ protected:
 	std::size_t _height{0};
 
 	bool _visible{true};
+
+	bool dirty{true};
 };
 
 } // GUI::
diff --git a/plugingui/window.cc b/plugingui/window.cc
index 98c0c80..44c8e12 100644
--- a/plugingui/window.cc
+++ b/plugingui/window.cc
@@ -42,7 +42,8 @@
 #include "nativewindow_pugl.h"
 #endif
 
-namespace GUI {
+namespace GUI
+{
 
 Window::Window(void* native_window)
 	: Widget(nullptr)
@@ -101,40 +102,10 @@ void Window::move(int x, int y)
 	native->move(x, y);
 }
 
-int Window::x()
-{
-	return native->getPosition().first;
-}
-
-int Window::y()
-{
-	return native->getPosition().second;
-}
-
-size_t Window::width()
-{
-	return native->getSize().first;
-}
-
-size_t Window::height()
-{
-	return native->getSize().second;
-}
-
-size_t Window::windowX()
-{
-	return 0;
-}
-
-size_t Window::windowY()
-{
-	return 0;
-}
-
 void Window::show()
 {
 	Widget::show();
-	repaintChildren(nullptr);
+	redraw();
 	native->show();
 }
 
@@ -171,12 +142,12 @@ void Window::setKeyboardFocus(Widget* widget)
 
 	if(oldFocusWidget)
 	{
-		oldFocusWidget->repaintEvent(nullptr);
+		oldFocusWidget->redraw();
 	}
 
 	if(_keyboardFocus)
 	{
-		_keyboardFocus->repaintEvent(nullptr);
+		_keyboardFocus->redraw();
 	}
 }
 
@@ -202,18 +173,34 @@ void Window::setMouseFocus(Widget* widget)
 
 }
 
-void Window::redraw()
+void Window::needsRedraw()
 {
-	native->redraw();
+	needs_redraw = true;
+}
+
+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)
 {
-	wpixbuf.realloc(this->width(), this->height());
-	Widget::resize(this->width(), this->height());
-	updateBuffer();
+	auto size = native->getSize();
+	if((wpixbuf.width == size.first) &&
+	   (wpixbuf.height == size.second))
+	{
+		return;
+	}
+	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
@@ -224,9 +211,20 @@ void Window::moved(int x, int y)
 	Widget::move(x, y);
 }
 
-void Window::updateBuffer()
+bool Window::updateBuffer()
 {
-	for(auto pixelBuffer : getPixelBuffers())
+	if(!native)
+	{
+		return false;
+	}
+
+	if(!needs_redraw)
+	{
+		// Nothing changed, don't update anything.
+		return false;
+	}
+
+	for(const auto& pixelBuffer : getPixelBuffers())
 	{
 		size_t updateWidth = pixelBuffer->width;
 		size_t updateHeight = pixelBuffer->height;
@@ -258,35 +256,10 @@ void Window::updateBuffer()
 		}
 	}
 
-	native->handleBuffer();
-}
-
-void Window::beginPaint()
-{
-	++refcount;
-	if(refcount > maxRefcount)
-	{
-		maxRefcount = refcount;
-	}
-}
-
-void Window::endPaint()
-{
-	if(refcount)
-	{
-		--refcount;
-	}
+	native->redraw();
+	needs_redraw = false;
 
-	if(!refcount)
-	{
-		// Did we go deep enough for a buffer update?
-		if(maxRefcount > 1)
-		{
-			updateBuffer();
-			redraw();
-		}
-		maxRefcount = 0;
-	}
+	return true;
 }
 
 } // GUI::
diff --git a/plugingui/window.h b/plugingui/window.h
index 0a10b3e..cfb004d 100644
--- a/plugingui/window.h
+++ b/plugingui/window.h
@@ -50,12 +50,6 @@ public:
 	// From Widget:
 	void resize(std::size_t width, std::size_t height) override;
 	void move(int x, int y) override;
-	int x() override;
-	int y() override;
-	size_t width() override;
-	size_t height() override;
-	size_t windowX() override;
-	size_t windowY() override;
 	void show() override;
 	void hide() override;
 	Window* window() override;
@@ -73,18 +67,25 @@ public:
 	Widget* mouseFocus();
 	void setMouseFocus(Widget* widget);
 
+	//! Tag the window buffer dirty to be rendered.
+	void needsRedraw();
+
 protected:
 	// For the EventHandler
 	friend class EventHandler;
-	void redraw();
+
+	// 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);
-	void updateBuffer();
+
+	//! Returns true if window pixel buffer changed and needs to be copied to
+	//! native window.
+	bool updateBuffer();
 
 	// For the Painter
 	friend class Widget;
-	void beginPaint() override;
-	void endPaint() override;
 
 	// For the NativeWindow
 	friend class NativeWindowX11;
@@ -103,6 +104,7 @@ protected:
 
 	size_t maxRefcount{0};
 
+	bool needs_redraw{false};
 	ImageCache image_cache;
 };
 
-- 
cgit v1.2.3