Serenity Operating System
at hosted 315 lines 15 kB view raw
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}