Serenity Operating System
at hosted 461 lines 14 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 <LibGUI/BoxLayout.h> 28#include <LibGUI/Button.h> 29#include <LibGUI/ColorPicker.h> 30#include <LibGUI/Frame.h> 31#include <LibGUI/Label.h> 32#include <LibGUI/Painter.h> 33#include <LibGUI/SpinBox.h> 34#include <LibGUI/TabWidget.h> 35#include <LibGUI/TextBox.h> 36#include <LibGfx/Palette.h> 37 38namespace GUI { 39 40class ColorButton : public AbstractButton { 41 C_OBJECT(ColorButton) 42 43public: 44 virtual ~ColorButton() override; 45 46 void set_selected(bool selected); 47 Color color() const { return m_color; } 48 49 Function<void(const Color)> on_click; 50 51protected: 52 virtual void click() override; 53 virtual void paint_event(PaintEvent&) override; 54 55private: 56 explicit ColorButton(Color color = {}); 57 58 Color m_color; 59 bool m_selected { false }; 60}; 61 62class CustomColorWidget final : public GUI::Widget { 63 C_OBJECT(CustomColorWidget); 64 65public: 66 Function<void(Color)> on_pick; 67 void clear_last_position(); 68 69private: 70 CustomColorWidget(); 71 72 RefPtr<Gfx::Bitmap> m_custom_colors; 73 bool m_status { false }; 74 Gfx::Point m_last_position; 75 76 void fire_event(GUI::MouseEvent& event); 77 78 virtual void mousedown_event(GUI::MouseEvent&) override; 79 virtual void mouseup_event(GUI::MouseEvent&) override; 80 virtual void mousemove_event(GUI::MouseEvent&) override; 81 virtual void paint_event(GUI::PaintEvent&) override; 82 virtual void resize_event(ResizeEvent&) override; 83}; 84 85ColorPicker::ColorPicker(Color color, Window* parent_window, String title) 86 : Dialog(parent_window) 87 , m_color(color) 88{ 89 set_icon(Gfx::Bitmap::load_from_file("/res/icons/16x16/color-chooser.png")); 90 set_title(title); 91 set_resizable(false); 92 resize(530, 325); 93 94 build_ui(); 95} 96 97ColorPicker::~ColorPicker() 98{ 99} 100 101void ColorPicker::build_ui() 102{ 103 auto& root_container = set_main_widget<Widget>(); 104 root_container.set_layout<VerticalBoxLayout>(); 105 root_container.layout()->set_margins({ 4, 4, 4, 4 }); 106 root_container.set_fill_with_background_color(true); 107 108 auto& tab_widget = root_container.add<GUI::TabWidget>(); 109 110 auto& tab_palette = tab_widget.add_tab<Widget>("Palette"); 111 tab_palette.set_size_policy(SizePolicy::Fill, SizePolicy::Fill); 112 tab_palette.set_layout<VerticalBoxLayout>(); 113 tab_palette.layout()->set_margins({ 4, 4, 4, 4 }); 114 tab_palette.layout()->set_spacing(4); 115 116 build_ui_palette(tab_palette); 117 118 auto& tab_custom_color = tab_widget.add_tab<Widget>("Custom Color"); 119 tab_custom_color.set_size_policy(SizePolicy::Fill, SizePolicy::Fill); 120 tab_custom_color.set_layout<VerticalBoxLayout>(); 121 tab_custom_color.layout()->set_margins({ 4, 4, 4, 4 }); 122 tab_custom_color.layout()->set_spacing(4); 123 124 build_ui_custom(tab_custom_color); 125 126 auto& button_container = root_container.add<Widget>(); 127 button_container.set_size_policy(SizePolicy::Fill, SizePolicy::Fixed); 128 button_container.set_preferred_size(0, 22); 129 button_container.set_layout<HorizontalBoxLayout>(); 130 button_container.layout()->set_spacing(4); 131 button_container.layout()->add_spacer(); 132 133 auto& cancel_button = button_container.add<Button>(); 134 cancel_button.set_size_policy(SizePolicy::Fixed, SizePolicy::Fill); 135 cancel_button.set_preferred_size(80, 0); 136 cancel_button.set_text("Cancel"); 137 cancel_button.on_click = [this] { 138 done(ExecCancel); 139 }; 140 141 auto& ok_button = button_container.add<Button>(); 142 ok_button.set_size_policy(SizePolicy::Fixed, SizePolicy::Fill); 143 ok_button.set_preferred_size(80, 0); 144 ok_button.set_text("Select"); 145 ok_button.on_click = [this] { 146 done(ExecOK); 147 }; 148} 149 150void ColorPicker::build_ui_palette(Widget& root_container) 151{ 152 unsigned colors[4][9] = { 153 { 0xef2929, 0xf0b143, 0xfce94f, 0x9fe13a, 0x7c9ece, 0xa680a8, 0xe1ba70, 0x888a85, 0xeeeeec }, 154 { 0xba1e09, 0xf57900, 0xe9d51a, 0x8bd121, 0x4164a3, 0x6f517b, 0xb77f19, 0x555753, 0xd4d7cf }, 155 { 0x961605, 0xbf600c, 0xe9d51a, 0x619910, 0x2b4986, 0x573666, 0x875b09, 0x2f3436, 0xbbbdb6 }, 156 { 0x000000, 0x2f3436, 0x555753, 0x808080, 0xbabdb6, 0xd3d7cf, 0xeeeeec, 0xf3f3f3, 0xffffff } 157 }; 158 159 for (int r = 0; r < 4; r++) { 160 auto& colors_row = root_container.add<Widget>(); 161 colors_row.set_layout<HorizontalBoxLayout>(); 162 colors_row.set_size_policy(SizePolicy::Fill, SizePolicy::Fill); 163 164 for (int i = 0; i < 8; i++) { 165 create_color_button(colors_row, colors[r][i]); 166 } 167 } 168} 169 170void ColorPicker::build_ui_custom(Widget& root_container) 171{ 172 enum RGBComponent { 173 Red, 174 Green, 175 Blue 176 }; 177 178 auto& horizontal_container = root_container.add<Widget>(); 179 horizontal_container.set_fill_with_background_color(true); 180 horizontal_container.set_layout<HorizontalBoxLayout>(); 181 182 // Left Side 183 m_custom_color = horizontal_container.add<GUI::CustomColorWidget>(); 184 m_custom_color->set_size_policy(SizePolicy::Fill, SizePolicy::Fill); 185 m_custom_color->on_pick = [this](Color color) { 186 if (m_color == color) 187 return; 188 189 m_color = color; 190 update_color_widgets(); 191 }; 192 193 // Right Side 194 auto& vertical_container = horizontal_container.add<Widget>(); 195 vertical_container.set_size_policy(SizePolicy::Fixed, SizePolicy::Fill); 196 vertical_container.set_layout<VerticalBoxLayout>(); 197 vertical_container.layout()->set_margins({ 4, 0, 0, 0 }); 198 vertical_container.set_preferred_size(150, 0); 199 200 // Preview 201 m_preview_widget = vertical_container.add<Frame>(); 202 m_preview_widget->set_size_policy(SizePolicy::Fill, SizePolicy::Fixed); 203 m_preview_widget->set_preferred_size(0, 150); 204 m_preview_widget->set_fill_with_background_color(true); 205 206 auto pal = m_preview_widget->palette(); 207 pal.set_color(ColorRole::Background, m_color); 208 m_preview_widget->set_palette(pal); 209 210 vertical_container.layout()->add_spacer(); 211 212 // HTML 213 auto& html_container = vertical_container.add<GUI::Widget>(); 214 html_container.set_layout<GUI::HorizontalBoxLayout>(); 215 html_container.set_size_policy(GUI::SizePolicy::Fill, GUI::SizePolicy::Fixed); 216 html_container.set_preferred_size(0, 22); 217 218 auto& html_label = html_container.add<GUI::Label>(); 219 html_label.set_text_alignment(Gfx::TextAlignment::CenterLeft); 220 html_label.set_size_policy(GUI::SizePolicy::Fixed, GUI::SizePolicy::Fill); 221 html_label.set_preferred_size({ 70, 0 }); 222 html_label.set_text("HTML:"); 223 224 m_html_text = html_container.add<GUI::TextBox>(); 225 m_html_text->set_size_policy(GUI::SizePolicy::Fill, GUI::SizePolicy::Fill); 226 m_html_text->set_text(m_color.to_string()); 227 m_html_text->on_change = [this]() { 228 auto color_name = this->m_html_text->text(); 229 auto optional_color = Color::from_string(color_name); 230 if (optional_color.has_value()) { 231 auto color = optional_color.value(); 232 if (m_color == color) 233 return; 234 235 m_color = optional_color.value(); 236 this->m_custom_color->clear_last_position(); 237 update_color_widgets(); 238 } 239 }; 240 241 // RGB Lines 242 auto make_spinbox = [&](RGBComponent component, int initial_value) { 243 auto& rgb_container = vertical_container.add<GUI::Widget>(); 244 rgb_container.set_layout<GUI::HorizontalBoxLayout>(); 245 rgb_container.set_size_policy(GUI::SizePolicy::Fill, GUI::SizePolicy::Fixed); 246 rgb_container.set_preferred_size(0, 22); 247 248 auto& rgb_label = rgb_container.add<GUI::Label>(); 249 rgb_label.set_text_alignment(Gfx::TextAlignment::CenterLeft); 250 rgb_label.set_size_policy(GUI::SizePolicy::Fixed, GUI::SizePolicy::Fill); 251 rgb_label.set_preferred_size({ 70, 0 }); 252 253 auto& spinbox = rgb_container.add<SpinBox>(); 254 spinbox.set_size_policy(SizePolicy::Fill, SizePolicy::Fixed); 255 spinbox.set_preferred_size(0, 20); 256 spinbox.set_min(0); 257 spinbox.set_max(255); 258 spinbox.set_value(initial_value); 259 spinbox.on_change = [this, component](auto value) { 260 auto color = m_color; 261 262 if (component == Red) 263 color.set_red(value); 264 if (component == Green) 265 color.set_green(value); 266 if (component == Blue) 267 color.set_blue(value); 268 269 if (m_color == color) 270 return; 271 272 m_color = color; 273 274 this->m_custom_color->clear_last_position(); 275 update_color_widgets(); 276 }; 277 278 if (component == Red) { 279 rgb_label.set_text("Red:"); 280 m_red_spinbox = spinbox; 281 } else if (component == Green) { 282 rgb_label.set_text("Green:"); 283 m_green_spinbox = spinbox; 284 } else if (component == Blue) { 285 rgb_label.set_text("Blue:"); 286 m_blue_spinbox = spinbox; 287 } 288 }; 289 290 make_spinbox(Red, m_color.red()); 291 make_spinbox(Green, m_color.green()); 292 make_spinbox(Blue, m_color.blue()); 293} 294 295void ColorPicker::update_color_widgets() 296{ 297 auto pal = m_preview_widget->palette(); 298 pal.set_color(ColorRole::Background, m_color); 299 m_preview_widget->set_palette(pal); 300 m_preview_widget->update(); 301 302 m_html_text->set_text(m_color.to_string()); 303 304 m_red_spinbox->set_value(m_color.red()); 305 m_green_spinbox->set_value(m_color.green()); 306 m_blue_spinbox->set_value(m_color.blue()); 307} 308 309void ColorPicker::create_color_button(Widget& container, unsigned rgb) 310{ 311 Color color = Color::from_rgb(rgb); 312 313 auto& widget = container.add<ColorButton>(color); 314 widget.set_size_policy(SizePolicy::Fill, SizePolicy::Fill); 315 widget.on_click = [this](Color color) { 316 for (auto& value : m_color_widgets) { 317 value->set_selected(false); 318 value->update(); 319 } 320 321 this->m_color = color; 322 }; 323 324 if (color == m_color) { 325 widget.set_selected(true); 326 } 327 328 m_color_widgets.append(&widget); 329} 330 331ColorButton::ColorButton(Color color) 332{ 333 m_color = color; 334 m_selected = false; 335} 336 337ColorButton::~ColorButton() 338{ 339} 340 341void ColorButton::set_selected(bool selected) 342{ 343 m_selected = selected; 344} 345 346void ColorButton::paint_event(PaintEvent& event) 347{ 348 Painter painter(*this); 349 painter.add_clip_rect(event.rect()); 350 351 Gfx::StylePainter::paint_button(painter, rect(), palette(), Gfx::ButtonStyle::Normal, is_being_pressed(), is_hovered(), is_checked(), is_enabled()); 352 353 painter.fill_rect({ 1, 1, rect().width() - 2, rect().height() - 2 }, m_color); 354 355 if (m_selected) { 356 painter.fill_rect({ 3, 3, rect().width() - 6, rect().height() - 6 }, Color::Black); 357 painter.fill_rect({ 5, 5, rect().width() - 10, rect().height() - 10 }, Color::White); 358 painter.fill_rect({ 7, 6, rect().width() - 14, rect().height() - 14 }, m_color); 359 } 360} 361 362void ColorButton::click() 363{ 364 if (on_click) 365 on_click(m_color); 366 367 m_selected = true; 368} 369 370CustomColorWidget::CustomColorWidget() 371{ 372 m_custom_colors = Gfx::Bitmap::create(Gfx::BitmapFormat::RGB32, { 360, 512 }); 373 auto painter = Gfx::Painter(*m_custom_colors); 374 375 for (int h = 0; h < 360; h++) { 376 Gfx::HSV hsv; 377 hsv.hue = h / 2; 378 379 hsv.saturation = 255; 380 for (int v = 0; v < 256; v++) { 381 hsv.value = v; 382 383 Color color = Color::from_hsv(hsv); 384 painter.set_pixel({ h, v }, color); 385 } 386 387 hsv.value = 255; 388 for (int s = 0; s < 256; s++) { 389 hsv.saturation = 255 - s; 390 391 Color color = Color::from_hsv(hsv); 392 painter.set_pixel({ h, 256 + s }, color); 393 } 394 } 395} 396 397void CustomColorWidget::clear_last_position() 398{ 399 m_last_position = { -1, -1 }; 400 update(); 401} 402 403void CustomColorWidget::fire_event(GUI::MouseEvent& event) 404{ 405 if (!m_status) 406 return; 407 408 if (!on_pick) 409 return; 410 411 auto position = event.position(); 412 if (!this->rect().contains(position)) { 413 return; 414 } 415 m_last_position = position; 416 417 auto color = this->window()->back_bitmap()->get_pixel(position); 418 on_pick(color); 419 420 update(); 421} 422 423void CustomColorWidget::mousedown_event(GUI::MouseEvent& event) 424{ 425 if (event.button() == GUI::MouseButton::Left) { 426 m_status = true; 427 } else { 428 m_status = false; 429 } 430} 431 432void CustomColorWidget::mouseup_event(GUI::MouseEvent& event) 433{ 434 fire_event(event); 435 m_status = false; 436} 437 438void CustomColorWidget::mousemove_event(GUI::MouseEvent& event) 439{ 440 fire_event(event); 441} 442 443void CustomColorWidget::paint_event(GUI::PaintEvent& event) 444{ 445 GUI::Painter painter(*this); 446 Gfx::Rect rect = event.rect(); 447 448 painter.add_clip_rect(rect); 449 450 painter.draw_scaled_bitmap(rect, *m_custom_colors, m_custom_colors->rect()); 451 452 painter.draw_line({ m_last_position.x(), 0 }, { m_last_position.x(), rect.height() }, Color::Black); 453 painter.draw_line({ 0, m_last_position.y() }, { rect.width(), m_last_position.y() }, Color::Black); 454} 455 456void CustomColorWidget::resize_event(ResizeEvent&) 457{ 458 clear_last_position(); 459} 460 461}