1// LAF Library
2// Copyright (C) 2020-2022 Igara Studio S.A.
3//
4// This file is released under the terms of the MIT license.
5// Read LICENSE.txt for more information.
6
7#include "gfx/hsv.h"
8#include "gfx/rgb.h"
9#include "os/os.h"
10
11#include <algorithm>
12#include <cstdarg>
13#include <cstdlib>
14#include <string>
15#include <vector>
16
17class PanWindow {
18public:
19 PanWindow(os::System* system)
20 : m_window(system->makeWindow(800, 600))
21 , m_scroll(0.0, 0.0)
22 , m_zoom(1.0)
23 , m_hasCapture(false) {
24 m_window->setCursor(os::NativeCursor::Arrow);
25 m_window->setTitle("Pan Viewport");
26 repaint();
27 m_window->setVisible(true);
28 }
29
30 bool processEvent(const os::Event& ev) {
31 switch (ev.type()) {
32
33 case os::Event::CloseWindow:
34 return false;
35
36 case os::Event::ResizeWindow:
37 repaint();
38 break;
39
40 case os::Event::MouseEnter: break;
41 case os::Event::MouseLeave: break;
42 case os::Event::MouseMove:
43 if (m_hasCapture) {
44 m_scroll = m_captureScroll
45 + gfx::PointF(ev.position() - m_capturePos);
46 repaint();
47 }
48 break;
49
50 case os::Event::MouseDown:
51 if (!m_hasCapture) {
52 m_window->setCursor(os::NativeCursor::Move);
53 m_window->captureMouse();
54 m_hasCapture = true;
55 m_capturePos = ev.position();
56 m_captureScroll = m_scroll;
57 }
58 break;
59
60 case os::Event::MouseUp:
61 if (m_hasCapture) {
62 m_window->setCursor(os::NativeCursor::Arrow);
63 m_window->releaseMouse();
64 m_hasCapture = false;
65 }
66 break;
67
68 case os::Event::MouseDoubleClick:
69 // Reset
70 m_scroll = gfx::PointF(0.0, 0.0);
71 m_zoom = 1.0;
72 repaint();
73 break;
74
75 case os::Event::MouseWheel:
76 if (ev.modifiers() & os::kKeyCtrlModifier) {
77 int z = (ev.wheelDelta().x + ev.wheelDelta().y);
78 setZoom(gfx::PointF(ev.position()),
79 m_zoom - z/10.0);
80 }
81 else if (ev.preciseWheel()) {
82 // TODO we have plans to change the sign of wheelDelta() when preciseWheel() is true
83 m_scroll += gfx::PointF(-ev.wheelDelta());
84 }
85 else {
86 m_scroll += gfx::PointF(-ev.wheelDelta().x*m_window->width()/32,
87 -ev.wheelDelta().y*m_window->height()/32);
88 }
89 repaint();
90 break;
91
92 case os::Event::TouchMagnify:
93 setZoom(gfx::PointF(ev.position()),
94 m_zoom + m_zoom * ev.magnification());
95 break;
96
97 case os::Event::KeyDown:
98 if (ev.scancode() == os::kKeyEsc)
99 return false;
100 // Toggle full-screen
101 else if (// F11 for Windows/Linux
102 (ev.scancode() == os::kKeyF11) ||
103 // Ctrl+Command+F for macOS
104 (ev.scancode() == os::kKeyF &&
105 ev.modifiers() == (os::kKeyCmdModifier | os::kKeyCtrlModifier))) {
106 m_window->setFullscreen(!m_window->isFullscreen());
107 }
108 break;
109
110 default:
111 // Do nothing
112 break;
113 }
114 return true;
115 }
116
117 void repaint() {
118 os::Surface* surface = m_window->surface();
119 os::SurfaceLock lock(surface);
120 const gfx::Rect rc(surface->bounds());
121
122 os::Paint p;
123 p.style(os::Paint::Fill);
124 p.color(gfx::rgba(32, 32, 32, 255));
125 surface->drawRect(rc, p);
126
127 p.style(os::Paint::Stroke);
128 p.color(gfx::rgba(255, 255, 200, 255));
129 {
130 gfx::RectF rc2(rc);
131 rc2.shrink(24);
132 rc2.offset(-center());
133 rc2 *= m_zoom;
134 rc2.offset(center());
135 rc2.offset(m_scroll);
136 surface->drawRect(rc2, p);
137
138 for (int i=1; i<8; ++i) {
139 int v = i * rc2.w / 8;
140 surface->drawLine(int(rc2.x + v), int(rc2.y),
141 int(rc2.x + v), int(rc2.y + rc2.h), p);
142 v = i * rc2.h / 8;
143 surface->drawLine(int(rc2.x), int(rc2.y + v),
144 int(rc2.x + rc2.w), int(rc2.y + v), p);
145 }
146 }
147
148 {
149 std::vector<char> buf(256);
150 std::sprintf(&buf[0], "Scroll=%.2f %.2f Zoom=%.2f", m_scroll.x, m_scroll.y, m_zoom);
151 p.style(os::Paint::Fill);
152 os::draw_text(surface, nullptr, &buf[0], gfx::Point(12, 12), &p);
153 }
154
155 m_window->invalidate();
156 m_window->swapBuffers();
157 }
158
159private:
160 void setZoom(const gfx::PointF& mousePos, double newZoom) {
161 double oldZoom = m_zoom;
162 m_zoom = std::clamp(newZoom, 0.01, 10.0);
163
164 // To calculate the new scroll value (m_scroll), we know that the
165 // mouse position (mousePos) will be the same with the old and the
166 // new scroll/zoom values.
167 //
168 // So:
169 // pivot = ((mousePos - m_scroll) - center()) / oldZoom
170 // mousePos = (pivot*m_zoom + center() + m_scroll)
171 //
172 // Then:
173 // mousePos = (mousePos - m_scroll - center()) * m_zoom / oldZoom + center() + m_scroll
174 // m_scroll = mousePos - (mousePos - m_scroll - center()) * m_zoom / oldZoom - center()
175 m_scroll = mousePos - (mousePos - m_scroll - center()) * m_zoom / oldZoom - center();
176
177 repaint();
178 }
179
180 gfx::Point center() const {
181 return gfx::Point(m_window->width()/2,
182 m_window->height()/2);
183 }
184
185 os::WindowRef m_window;
186 gfx::PointF m_scroll;
187 double m_zoom;
188
189 // To pan the viewport with drag & drop
190 bool m_hasCapture;
191 gfx::Point m_capturePos;
192 gfx::PointF m_captureScroll;
193};
194
195int app_main(int argc, char* argv[])
196{
197 os::SystemRef system = os::make_system();
198 system->setAppMode(os::AppMode::GUI);
199 system->setGpuAcceleration(true);
200
201 PanWindow window(system.get());
202
203 system->handleWindowResize = [&window](os::Window* win){
204 window.repaint();
205 };
206
207 system->finishLaunching();
208 system->activateApp();
209
210 os::EventQueue* queue = system->eventQueue();
211 while (true) {
212 os::Event ev;
213 queue->getEvent(ev);
214 if (!window.processEvent(ev))
215 break;
216 }
217
218 return 0;
219}
220