Serenity Operating System
1/*
2 * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are met:
7 *
8 * 1. Redistributions of source code must retain the above copyright notice, this
9 * list of conditions and the following disclaimer.
10 *
11 * 2. Redistributions in binary form must reproduce the above copyright notice,
12 * this list of conditions and the following disclaimer in the documentation
13 * and/or other materials provided with the distribution.
14 *
15 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
16 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
18 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
19 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
21 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
22 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
23 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
24 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25 */
26
27#include <LibGfx/Bitmap.h>
28#include <LibGfx/Painter.h>
29#include <LibGfx/Palette.h>
30#include <LibGfx/StylePainter.h>
31
32namespace Gfx {
33
34void StylePainter::paint_tab_button(Painter& painter, const Rect& rect, const Palette& palette, bool active, bool hovered, bool enabled)
35{
36 Color base_color = palette.button();
37 Color highlight_color2 = palette.threed_highlight();
38 Color shadow_color1 = palette.threed_shadow1();
39 Color shadow_color2 = palette.threed_shadow2();
40
41 if (hovered && enabled && !active)
42 base_color = palette.hover_highlight();
43
44 PainterStateSaver saver(painter);
45 painter.translate(rect.location());
46
47 // Base
48 painter.fill_rect({ 1, 1, rect.width() - 2, rect.height() - 1 }, base_color);
49
50 // Top line
51 painter.draw_line({ 2, 0 }, { rect.width() - 3, 0 }, highlight_color2);
52
53 // Left side
54 painter.draw_line({ 0, 2 }, { 0, rect.height() - 1 }, highlight_color2);
55 painter.set_pixel({ 1, 1 }, highlight_color2);
56
57 // Right side
58 painter.draw_line({
59 rect.width() - 1,
60 2,
61 },
62 { rect.width() - 1, rect.height() - 1 }, shadow_color2);
63 painter.draw_line({
64 rect.width() - 2,
65 2,
66 },
67 { rect.width() - 2, rect.height() - 1 }, shadow_color1);
68 painter.set_pixel({
69 rect.width() - 2,
70 1,
71 },
72 shadow_color2);
73}
74
75static void paint_button_new(Painter& painter, const Rect& rect, const Palette& palette, bool pressed, bool checked, bool hovered, bool enabled)
76{
77 Color button_color = palette.button();
78 Color highlight_color2 = palette.threed_highlight();
79 Color shadow_color1 = palette.threed_shadow1();
80 Color shadow_color2 = palette.threed_shadow2();
81
82 if (checked && enabled) {
83 if (hovered)
84 button_color = palette.hover_highlight();
85 else
86 button_color = palette.button();
87 } else if (hovered && enabled)
88 button_color = palette.hover_highlight();
89
90 PainterStateSaver saver(painter);
91 painter.translate(rect.location());
92
93 if (pressed || checked) {
94 // Base
95 painter.fill_rect({ 1, 1, rect.width() - 2, rect.height() - 2 }, button_color);
96
97 painter.draw_rect({ {}, rect.size() }, shadow_color2);
98
99 // Sunken shadow
100 painter.draw_line({ 1, 1 }, { rect.width() - 2, 1 }, shadow_color1);
101 painter.draw_line({ 1, 2 }, { 1, rect.height() - 2 }, shadow_color1);
102 } else {
103 // Base
104 painter.fill_rect({ 1, 1, rect.width() - 3, rect.height() - 3 }, button_color);
105
106 // Outer highlight
107 painter.draw_line({ 0, 0 }, { rect.width() - 2, 0 }, highlight_color2);
108 painter.draw_line({ 0, 1 }, { 0, rect.height() - 2 }, highlight_color2);
109
110 // Outer shadow
111 painter.draw_line({ 0, rect.height() - 1 }, { rect.width() - 1, rect.height() - 1 }, shadow_color2);
112 painter.draw_line({ rect.width() - 1, 0 }, { rect.width() - 1, rect.height() - 2 }, shadow_color2);
113
114 // Inner shadow
115 painter.draw_line({ 1, rect.height() - 2 }, { rect.width() - 2, rect.height() - 2 }, shadow_color1);
116 painter.draw_line({ rect.width() - 2, 1 }, { rect.width() - 2, rect.height() - 3 }, shadow_color1);
117 }
118}
119
120void StylePainter::paint_button(Painter& painter, const Rect& rect, const Palette& palette, ButtonStyle button_style, bool pressed, bool hovered, bool checked, bool enabled)
121{
122 if (button_style == ButtonStyle::Normal)
123 return paint_button_new(painter, rect, palette, pressed, checked, hovered, enabled);
124
125 Color button_color = palette.button();
126 Color highlight_color = palette.threed_highlight();
127 Color shadow_color = palette.threed_shadow1();
128
129 if (button_style == ButtonStyle::CoolBar && !enabled)
130 return;
131
132 PainterStateSaver saver(painter);
133 painter.translate(rect.location());
134
135 if (pressed || checked) {
136 // Base
137 painter.fill_rect({ 1, 1, rect.width() - 2, rect.height() - 2 }, button_color);
138
139 // Sunken shadow
140 painter.draw_line({ 1, 1 }, { rect.width() - 2, 1 }, shadow_color);
141 painter.draw_line({ 1, 2 }, { 1, rect.height() - 2 }, shadow_color);
142
143 // Bottom highlight
144 painter.draw_line({ rect.width() - 2, 1 }, { rect.width() - 2, rect.height() - 3 }, highlight_color);
145 painter.draw_line({ 1, rect.height() - 2 }, { rect.width() - 2, rect.height() - 2 }, highlight_color);
146 } else if (button_style == ButtonStyle::CoolBar && hovered) {
147 // Base
148 painter.fill_rect({ 1, 1, rect.width() - 2, rect.height() - 2 }, button_color);
149
150 // White highlight
151 painter.draw_line({ 1, 1 }, { rect.width() - 2, 1 }, highlight_color);
152 painter.draw_line({ 1, 2 }, { 1, rect.height() - 2 }, highlight_color);
153
154 // Gray shadow
155 painter.draw_line({ rect.width() - 2, 1 }, { rect.width() - 2, rect.height() - 3 }, shadow_color);
156 painter.draw_line({ 1, rect.height() - 2 }, { rect.width() - 2, rect.height() - 2 }, shadow_color);
157 }
158}
159
160void StylePainter::paint_surface(Painter& painter, const Rect& rect, const Palette& palette, bool paint_vertical_lines, bool paint_top_line)
161{
162 painter.fill_rect({ rect.x(), rect.y() + 1, rect.width(), rect.height() - 2 }, palette.button());
163 painter.draw_line(rect.top_left(), rect.top_right(), paint_top_line ? palette.threed_highlight() : palette.button());
164 painter.draw_line(rect.bottom_left(), rect.bottom_right(), palette.threed_shadow1());
165 if (paint_vertical_lines) {
166 painter.draw_line(rect.top_left().translated(0, 1), rect.bottom_left().translated(0, -1), palette.threed_highlight());
167 painter.draw_line(rect.top_right(), rect.bottom_right().translated(0, -1), palette.threed_shadow1());
168 }
169}
170
171void StylePainter::paint_frame(Painter& painter, const Rect& rect, const Palette& palette, FrameShape shape, FrameShadow shadow, int thickness, bool skip_vertical_lines)
172{
173 Color top_left_color;
174 Color bottom_right_color;
175 Color dark_shade = palette.threed_shadow1();
176 Color light_shade = palette.threed_highlight();
177
178 if (shape == FrameShape::Container && thickness >= 2) {
179 if (shadow == FrameShadow::Raised) {
180 dark_shade = palette.threed_shadow2();
181 }
182 }
183
184 if (shadow == FrameShadow::Raised) {
185 top_left_color = light_shade;
186 bottom_right_color = dark_shade;
187 } else if (shadow == FrameShadow::Sunken) {
188 top_left_color = dark_shade;
189 bottom_right_color = light_shade;
190 } else if (shadow == FrameShadow::Plain) {
191 top_left_color = dark_shade;
192 bottom_right_color = dark_shade;
193 }
194
195 if (thickness >= 1) {
196 painter.draw_line(rect.top_left(), rect.top_right(), top_left_color);
197 painter.draw_line(rect.bottom_left(), rect.bottom_right(), bottom_right_color);
198
199 if (shape != FrameShape::Panel || !skip_vertical_lines) {
200 painter.draw_line(rect.top_left().translated(0, 1), rect.bottom_left().translated(0, -1), top_left_color);
201 painter.draw_line(rect.top_right(), rect.bottom_right().translated(0, -1), bottom_right_color);
202 }
203 }
204
205 if (shape == FrameShape::Container && thickness >= 2) {
206 Color top_left_color;
207 Color bottom_right_color;
208 Color dark_shade = palette.threed_shadow2();
209 Color light_shade = palette.button();
210 if (shadow == FrameShadow::Raised) {
211 dark_shade = palette.threed_shadow1();
212 top_left_color = light_shade;
213 bottom_right_color = dark_shade;
214 } else if (shadow == FrameShadow::Sunken) {
215 top_left_color = dark_shade;
216 bottom_right_color = light_shade;
217 } else if (shadow == FrameShadow::Plain) {
218 top_left_color = dark_shade;
219 bottom_right_color = dark_shade;
220 }
221 Rect inner_container_frame_rect = rect.shrunken(2, 2);
222 painter.draw_line(inner_container_frame_rect.top_left(), inner_container_frame_rect.top_right(), top_left_color);
223 painter.draw_line(inner_container_frame_rect.bottom_left(), inner_container_frame_rect.bottom_right(), bottom_right_color);
224 painter.draw_line(inner_container_frame_rect.top_left().translated(0, 1), inner_container_frame_rect.bottom_left().translated(0, -1), top_left_color);
225 painter.draw_line(inner_container_frame_rect.top_right(), inner_container_frame_rect.bottom_right().translated(0, -1), bottom_right_color);
226 }
227
228 if (shape == FrameShape::Box && thickness >= 2) {
229 swap(top_left_color, bottom_right_color);
230 Rect inner_rect = rect.shrunken(2, 2);
231 painter.draw_line(inner_rect.top_left(), inner_rect.top_right(), top_left_color);
232 painter.draw_line(inner_rect.bottom_left(), inner_rect.bottom_right(), bottom_right_color);
233 painter.draw_line(inner_rect.top_left().translated(0, 1), inner_rect.bottom_left().translated(0, -1), top_left_color);
234 painter.draw_line(inner_rect.top_right(), inner_rect.bottom_right().translated(0, -1), bottom_right_color);
235 }
236}
237
238void StylePainter::paint_window_frame(Painter& painter, const Rect& rect, const Palette& palette)
239{
240 Color base_color = palette.button();
241 Color dark_shade = palette.threed_shadow2();
242 Color mid_shade = palette.threed_shadow1();
243 Color light_shade = palette.threed_highlight();
244
245 painter.draw_line(rect.top_left(), rect.top_right(), base_color);
246 painter.draw_line(rect.top_left().translated(0, 1), rect.bottom_left(), base_color);
247 painter.draw_line(rect.top_left().translated(1, 1), rect.top_right().translated(-1, 1), light_shade);
248 painter.draw_line(rect.top_left().translated(1, 1), rect.bottom_left().translated(1, -1), light_shade);
249 painter.draw_line(rect.top_left().translated(2, 2), rect.top_right().translated(-2, 2), base_color);
250 painter.draw_line(rect.top_left().translated(2, 2), rect.bottom_left().translated(2, -2), base_color);
251
252 painter.draw_line(rect.top_right(), rect.bottom_right(), dark_shade);
253 painter.draw_line(rect.top_right().translated(-1, 1), rect.bottom_right().translated(-1, -1), mid_shade);
254 painter.draw_line(rect.top_right().translated(-2, 2), rect.bottom_right().translated(-2, -2), base_color);
255 painter.draw_line(rect.bottom_left(), rect.bottom_right(), dark_shade);
256 painter.draw_line(rect.bottom_left().translated(1, -1), rect.bottom_right().translated(-1, -1), mid_shade);
257 painter.draw_line(rect.bottom_left().translated(2, -2), rect.bottom_right().translated(-2, -2), base_color);
258}
259
260void StylePainter::paint_progress_bar(Painter& painter, const Rect& rect, const Palette& palette, int min, int max, int value, const StringView& text)
261{
262 // First we fill the entire widget with the gradient. This incurs a bit of
263 // overdraw but ensures a consistent look throughout the progression.
264 Color start_color = palette.active_window_border1();
265 Color end_color = palette.active_window_border2();
266 painter.fill_rect_with_gradient(rect, start_color, end_color);
267
268 if (!text.is_null()) {
269 painter.draw_text(rect.translated(1, 1), text, TextAlignment::Center, palette.base_text());
270 painter.draw_text(rect, text, TextAlignment::Center, palette.base_text().inverted());
271 }
272
273 float range_size = max - min;
274 float progress = (value - min) / range_size;
275
276 // Then we carve out a hole in the remaining part of the widget.
277 // We draw the text a third time, clipped and inverse, for sharp contrast.
278 float progress_width = progress * rect.width();
279 Rect hole_rect { (int)progress_width, 0, (int)(rect.width() - progress_width), rect.height() };
280 hole_rect.move_by(rect.location());
281 hole_rect.set_right_without_resize(rect.right());
282 PainterStateSaver saver(painter);
283 painter.fill_rect(hole_rect, palette.base());
284
285 painter.add_clip_rect(hole_rect);
286 if (!text.is_null())
287 painter.draw_text(rect.translated(0, 0), text, TextAlignment::Center, palette.base_text());
288}
289
290static RefPtr<Gfx::Bitmap> s_unfilled_circle_bitmap;
291static RefPtr<Gfx::Bitmap> s_filled_circle_bitmap;
292static RefPtr<Gfx::Bitmap> s_changing_filled_circle_bitmap;
293static RefPtr<Gfx::Bitmap> s_changing_unfilled_circle_bitmap;
294
295static const Gfx::Bitmap& circle_bitmap(bool checked, bool changing)
296{
297 if (changing)
298 return checked ? *s_changing_filled_circle_bitmap : *s_changing_unfilled_circle_bitmap;
299 return checked ? *s_filled_circle_bitmap : *s_unfilled_circle_bitmap;
300}
301
302void StylePainter::paint_radio_button(Painter& painter, const Rect& rect, const Palette&, bool is_checked, bool is_being_pressed)
303{
304 if (!s_unfilled_circle_bitmap) {
305 s_unfilled_circle_bitmap = Bitmap::load_from_file("/res/icons/unfilled-radio-circle.png");
306 s_filled_circle_bitmap = Bitmap::load_from_file("/res/icons/filled-radio-circle.png");
307 s_changing_filled_circle_bitmap = Bitmap::load_from_file("/res/icons/changing-filled-radio-circle.png");
308 s_changing_unfilled_circle_bitmap = Bitmap::load_from_file("/res/icons/changing-unfilled-radio-circle.png");
309 }
310
311 auto& bitmap = circle_bitmap(is_checked, is_being_pressed);
312 painter.blit(rect.location(), bitmap, bitmap.rect());
313}
314
315}