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 | |
17 | class PanWindow { |
18 | public: |
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 | |
159 | private: |
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 | |
195 | int 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 | |