Serenity Operating System
1/*
2 * Copyright (c) 2021, the SerenityOS developers.
3 *
4 * SPDX-License-Identifier: BSD-2-Clause
5 */
6
7#include "Overlays.h"
8#include "Compositor.h"
9#include "WindowManager.h"
10#include <LibGfx/StylePainter.h>
11
12namespace WindowServer {
13
14Overlay::~Overlay()
15{
16 Compositor::the().remove_overlay(*this);
17}
18
19bool Overlay::invalidate()
20{
21 if (m_invalidated)
22 return false;
23 m_invalidated = true;
24 // m_current_rect should only get updated by recompute_overlay_rects()
25 if (!m_current_rect.is_empty())
26 Compositor::the().invalidate_screen(m_current_rect);
27 return true;
28}
29
30void Overlay::set_enabled(bool enable)
31{
32 if (is_enabled() == enable)
33 return;
34
35 if (enable)
36 Compositor::the().add_overlay(*this);
37 else
38 Compositor::the().remove_overlay(*this);
39}
40
41void Overlay::set_rect(Gfx::IntRect const& rect)
42{
43 if (m_rect == rect)
44 return;
45 auto previous_rect = m_rect;
46 m_rect = rect;
47 invalidate();
48 if (is_enabled())
49 Compositor::the().overlay_rects_changed();
50 rect_changed(previous_rect);
51}
52
53BitmapOverlay::BitmapOverlay()
54{
55 clear_bitmaps();
56}
57
58void BitmapOverlay::rect_changed(Gfx::IntRect const& previous_rect)
59{
60 if (rect().size() != previous_rect.size())
61 clear_bitmaps();
62 Overlay::rect_changed(previous_rect);
63}
64
65void BitmapOverlay::clear_bitmaps()
66{
67 m_bitmaps = MultiScaleBitmaps::create_empty();
68}
69
70void BitmapOverlay::render(Gfx::Painter& painter, Screen const& screen)
71{
72 auto scale_factor = screen.scale_factor();
73 auto* bitmap = m_bitmaps->find_bitmap(scale_factor);
74 if (!bitmap) {
75 auto new_bitmap = create_bitmap(scale_factor);
76 if (!new_bitmap)
77 return;
78 bitmap = new_bitmap.ptr();
79 m_bitmaps->add_bitmap(scale_factor, new_bitmap.release_nonnull());
80 }
81
82 painter.blit({}, *bitmap, bitmap->rect());
83}
84
85RectangularOverlay::RectangularOverlay()
86{
87 clear_bitmaps();
88}
89
90void RectangularOverlay::rect_changed(Gfx::IntRect const& previous_rect)
91{
92 if (rect().size() != previous_rect.size())
93 clear_bitmaps();
94}
95
96void RectangularOverlay::clear_bitmaps()
97{
98 m_rendered_bitmaps = MultiScaleBitmaps::create_empty();
99}
100
101void RectangularOverlay::render(Gfx::Painter& painter, Screen const& screen)
102{
103 if (m_content_invalidated) {
104 clear_bitmaps();
105 m_content_invalidated = false;
106 }
107 auto scale_factor = screen.scale_factor();
108 auto* bitmap = m_rendered_bitmaps->find_bitmap(scale_factor);
109 if (!bitmap) {
110 auto bitmap_or_error = Gfx::Bitmap::create(Gfx::BitmapFormat::BGRA8888, rect().size(), scale_factor);
111 if (bitmap_or_error.is_error())
112 return;
113 auto new_bitmap = bitmap_or_error.release_value_but_fixme_should_propagate_errors();
114 bitmap = new_bitmap.ptr();
115
116 Gfx::Painter bitmap_painter(*new_bitmap);
117 if (auto* shadow_bitmap = WindowManager::the().overlay_rect_shadow()) {
118 Gfx::StylePainter::paint_simple_rect_shadow(bitmap_painter, new_bitmap->rect(), shadow_bitmap->bitmap(scale_factor), true, true);
119 } else {
120 bitmap_painter.fill_rect(new_bitmap->rect(), Color(Color::Black).with_alpha(0xcc));
121 }
122 render_overlay_bitmap(bitmap_painter);
123 m_rendered_bitmaps->add_bitmap(scale_factor, move(new_bitmap));
124 }
125
126 painter.blit({}, *bitmap, bitmap->rect());
127}
128
129Gfx::IntRect RectangularOverlay::calculate_frame_rect(Gfx::IntRect const& rect)
130{
131 if (auto* shadow_bitmap = WindowManager::the().overlay_rect_shadow()) {
132 Gfx::IntSize size;
133 int base_size = shadow_bitmap->default_bitmap().height() / 2;
134 size = { base_size, base_size };
135 return rect.inflated(2 * base_size, 2 * base_size);
136 }
137 return rect.inflated(2 * default_frame_thickness, 2 * default_frame_thickness);
138}
139
140void RectangularOverlay::set_content_rect(Gfx::IntRect const& rect)
141{
142 set_rect(calculate_frame_rect(rect));
143}
144
145void RectangularOverlay::invalidate_content()
146{
147 m_content_invalidated = true;
148 invalidate();
149}
150
151Gfx::Font const* ScreenNumberOverlay::s_font { nullptr };
152
153ScreenNumberOverlay::ScreenNumberOverlay(Screen& screen)
154 : m_screen(screen)
155{
156 if (!s_font)
157 pick_font();
158
159 Gfx::IntRect rect {
160 default_offset,
161 default_offset,
162 default_size,
163 default_size
164 };
165 rect.translate_by(screen.rect().location());
166 set_rect(rect);
167}
168
169void ScreenNumberOverlay::pick_font()
170{
171 auto screen_number_content_rect_size = calculate_content_rect_for_screen(Screen::main()).size();
172 auto& font_database = Gfx::FontDatabase::the();
173 auto& default_font = WindowManager::the().font();
174 DeprecatedString best_font_name;
175 int best_font_size = -1;
176 font_database.for_each_font([&](Gfx::Font const& font) {
177 // TODO: instead of picking *any* font we should probably compare font.name()
178 // with default_font.name(). But the default font currently does not provide larger sizes
179 auto size = font.pixel_size_rounded_up();
180 if (size * 2 <= screen_number_content_rect_size.height() && size > best_font_size) {
181 for (unsigned ch = '0'; ch <= '9'; ch++) {
182 if (!font.contains_glyph(ch)) {
183 // Skip this font, it doesn't have glyphs for digits
184 return;
185 }
186 }
187 best_font_name = font.qualified_name();
188 best_font_size = size;
189 }
190 });
191
192 if (auto best_font = font_database.get_by_name(best_font_name)) {
193 s_font = best_font.ptr();
194 } else {
195 s_font = &default_font;
196 }
197
198 Compositor::the().for_each_overlay([&](auto& overlay) {
199 if (overlay.zorder() == ZOrder::ScreenNumber)
200 overlay.invalidate();
201 return IterationDecision::Continue;
202 });
203}
204
205Gfx::Font const& ScreenNumberOverlay::font()
206{
207 if (!s_font) {
208 pick_font();
209 VERIFY(s_font);
210 }
211 return *s_font;
212}
213
214void ScreenNumberOverlay::render_overlay_bitmap(Gfx::Painter& painter)
215{
216 painter.draw_text(Gfx::IntRect { {}, rect().size() }, DeprecatedString::formatted("{}", m_screen.index() + 1), font(), Gfx::TextAlignment::Center, Color::White);
217}
218
219Gfx::IntRect ScreenNumberOverlay::calculate_content_rect_for_screen(Screen& screen)
220{
221 Gfx::IntRect content_rect {
222 screen.rect().location().translated(default_offset, default_offset),
223 { default_size, default_size }
224 };
225
226 return calculate_frame_rect(content_rect);
227}
228
229WindowGeometryOverlay::WindowGeometryOverlay(Window& window)
230 : m_window(window)
231{
232 update_rect();
233}
234
235void WindowGeometryOverlay::update_rect()
236{
237 if (auto* window = m_window.ptr()) {
238 auto& wm = WindowManager::the();
239 if (!window->size_increment().is_empty()) {
240 int width_steps = (window->width() - window->base_size().width()) / window->size_increment().width();
241 int height_steps = (window->height() - window->base_size().height()) / window->size_increment().height();
242 m_label = DeprecatedString::formatted("{} ({}x{})", window->rect(), width_steps, height_steps);
243 } else {
244 m_label = window->rect().to_deprecated_string();
245 }
246 m_label_rect = Gfx::IntRect { 0, 0, static_cast<int>(ceilf(wm.font().width(m_label))) + 16, wm.font().pixel_size_rounded_up() + 10 };
247
248 auto rect = calculate_frame_rect(m_label_rect).centered_within(window->frame().rect());
249 auto desktop_rect = wm.desktop_rect(ScreenInput::the().cursor_location_screen());
250 if (rect.left() < desktop_rect.left())
251 rect.set_left(desktop_rect.left());
252 if (rect.top() < desktop_rect.top())
253 rect.set_top(desktop_rect.top());
254 if (rect.right() > desktop_rect.right())
255 rect.set_right_without_resize(desktop_rect.right());
256 if (rect.bottom() > desktop_rect.bottom())
257 rect.set_bottom_without_resize(desktop_rect.bottom());
258
259 set_rect(rect);
260 } else {
261 set_enabled(false);
262 }
263}
264
265void WindowGeometryOverlay::render_overlay_bitmap(Gfx::Painter& painter)
266{
267 painter.draw_text(Gfx::IntRect { {}, rect().size() }, m_label, WindowManager::the().font(), Gfx::TextAlignment::Center, Color::White);
268}
269
270void WindowGeometryOverlay::window_rect_changed()
271{
272 update_rect();
273 invalidate_content();
274}
275
276DndOverlay::DndOverlay(DeprecatedString const& text, Gfx::Bitmap const* bitmap)
277 : m_bitmap(bitmap)
278 , m_text(text)
279{
280 update_rect();
281}
282
283Gfx::Font const& DndOverlay::font()
284{
285 return WindowManager::the().font();
286}
287
288void DndOverlay::update_rect()
289{
290 int bitmap_width = m_bitmap ? m_bitmap->width() : 0;
291 int bitmap_height = m_bitmap ? m_bitmap->height() : 0;
292 auto& font = this->font();
293 int width = font.width(m_text) + bitmap_width;
294 int height = max(font.pixel_size_rounded_up(), bitmap_height);
295 auto location = ScreenInput::the().cursor_location().translated(8, 8);
296 set_rect(Gfx::IntRect(location, { width, height }).inflated(16, 8));
297}
298
299RefPtr<Gfx::Bitmap> DndOverlay::create_bitmap(int scale_factor)
300{
301 auto bitmap_or_error = Gfx::Bitmap::create(Gfx::BitmapFormat::BGRA8888, rect().size(), scale_factor);
302 if (bitmap_or_error.is_error())
303 return {};
304 auto new_bitmap = bitmap_or_error.release_value_but_fixme_should_propagate_errors();
305
306 auto& wm = WindowManager::the();
307 Gfx::Painter bitmap_painter(*new_bitmap);
308 auto bitmap_rect = new_bitmap->rect();
309 bitmap_painter.fill_rect(bitmap_rect, wm.palette().selection().with_alpha(200));
310 bitmap_painter.draw_rect(bitmap_rect, wm.palette().selection());
311 if (!m_text.is_empty()) {
312 auto text_rect = bitmap_rect;
313 if (m_bitmap)
314 text_rect.translate_by(m_bitmap->width() + 8, 0);
315 bitmap_painter.draw_text(text_rect, m_text, Gfx::TextAlignment::CenterLeft, wm.palette().selection_text());
316 }
317 if (m_bitmap)
318 bitmap_painter.blit(bitmap_rect.top_left().translated(4, 4), *m_bitmap, m_bitmap->rect());
319 return new_bitmap;
320}
321
322void WindowStackSwitchOverlay::render_overlay_bitmap(Gfx::Painter& painter)
323{
324 // We should come up with a more elegant way to get the content rectangle
325 auto content_rect = Gfx::IntRect({}, m_content_size).centered_within({ {}, rect().size() });
326 auto active_color = WindowManager::the().palette().selection();
327 auto inactive_color = WindowManager::the().palette().window().darkened(0.9f);
328 for (int y = 0; y < m_rows; y++) {
329 for (int x = 0; x < m_columns; x++) {
330 Gfx::IntRect rect {
331 content_rect.left() + x * (default_screen_rect_width + default_screen_rect_padding),
332 content_rect.top() + y * (default_screen_rect_height + default_screen_rect_padding),
333 default_screen_rect_width,
334 default_screen_rect_height
335 };
336 bool is_target = y == m_target_row && x == m_target_column;
337 painter.fill_rect(rect, is_target ? active_color : inactive_color);
338 }
339 }
340}
341
342WindowStackSwitchOverlay::WindowStackSwitchOverlay(Screen& screen, WindowStack& target_window_stack)
343 : m_rows((int)WindowManager::the().window_stack_rows())
344 , m_columns((int)WindowManager::the().window_stack_columns())
345 , m_target_row((int)target_window_stack.row())
346 , m_target_column((int)target_window_stack.column())
347{
348 m_content_size = {
349 m_columns * (default_screen_rect_width + default_screen_rect_padding) - default_screen_rect_padding,
350 m_rows * (default_screen_rect_height + default_screen_rect_padding) - default_screen_rect_padding,
351 };
352 set_rect(calculate_frame_rect(Gfx::IntRect({}, m_content_size).inflated(2 * default_screen_rect_margin, 2 * default_screen_rect_margin)).centered_within(screen.rect()));
353}
354
355}