Serenity Operating System
1/*
2 * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
3 * Copyright (c) 2021, Felix Rauch <noreply@felixrau.ch>
4 * Copyright (c) 2021, Mustafa Quraish <mustafa@serenityos.org>
5 * Copyright (c) 2022, the SerenityOS developers.
6 *
7 * SPDX-License-Identifier: BSD-2-Clause
8 */
9
10#include "PaletteWidget.h"
11#include "ImageEditor.h"
12#include <AK/Result.h>
13#include <AK/Vector.h>
14#include <LibGUI/BoxLayout.h>
15#include <LibGUI/ColorPicker.h>
16#include <LibGUI/MessageBox.h>
17#include <LibGfx/Palette.h>
18#include <string.h>
19
20REGISTER_WIDGET(PixelPaint, PaletteWidget);
21
22namespace PixelPaint {
23
24class ColorWidget : public GUI::Frame {
25 C_OBJECT(ColorWidget);
26
27public:
28 virtual ~ColorWidget() override = default;
29
30 virtual Color color() { return m_color; }
31
32 virtual void mousedown_event(GUI::MouseEvent& event) override
33 {
34 if (event.modifiers() & KeyModifier::Mod_Ctrl) {
35 auto dialog = GUI::ColorPicker::construct(m_color, window());
36 if (dialog->exec() == GUI::Dialog::ExecResult::OK) {
37 m_color = dialog->color();
38 auto pal = palette();
39 pal.set_color(ColorRole::Background, m_color);
40 set_palette(pal);
41 update();
42 }
43 }
44
45 if (event.button() == GUI::MouseButton::Primary)
46 m_palette_widget.set_primary_color(m_color);
47 else if (event.button() == GUI::MouseButton::Secondary)
48 m_palette_widget.set_secondary_color(m_color);
49 }
50
51private:
52 explicit ColorWidget(Color color, PaletteWidget& palette_widget)
53 : m_palette_widget(palette_widget)
54 , m_color(color)
55 {
56 set_fixed_width(16);
57 }
58
59 PaletteWidget& m_palette_widget;
60 Color m_color;
61};
62
63class SelectedColorWidget : public GUI::Frame {
64 C_OBJECT(SelectedColorWidget);
65
66public:
67 virtual ~SelectedColorWidget() override = default;
68
69 virtual void mousedown_event(GUI::MouseEvent& event) override
70 {
71 if (event.button() != GUI::MouseButton::Primary || !on_color_change)
72 return;
73
74 auto dialog = GUI::ColorPicker::construct(m_color, window());
75 if (dialog->exec() == GUI::Dialog::ExecResult::OK)
76 on_color_change(dialog->color());
77 }
78
79 void set_background_color(Color color)
80 {
81 auto pal = palette();
82 pal.set_color(ColorRole::Background, color);
83 set_palette(pal);
84 update();
85 m_color = color;
86 }
87
88 Function<void(Color)> on_color_change;
89 Color m_color = Color::White;
90
91private:
92 SelectedColorWidget() = default;
93};
94
95PaletteWidget::PaletteWidget()
96{
97 set_frame_shape(Gfx::FrameShape::Panel);
98 set_frame_shadow(Gfx::FrameShadow::Raised);
99 set_frame_thickness(0);
100 set_fill_with_background_color(true);
101
102 set_fixed_height(35);
103
104 m_secondary_color_widget = add<SelectedColorWidget>();
105 m_secondary_color_widget->on_color_change = [&](auto color) {
106 set_secondary_color(color);
107 };
108 m_secondary_color_widget->set_relative_rect({ 0, 2, 60, 33 });
109 m_secondary_color_widget->set_fill_with_background_color(true);
110
111 m_primary_color_widget = add<SelectedColorWidget>();
112 m_primary_color_widget->on_color_change = [&](auto color) {
113 set_primary_color(color);
114 };
115 auto rect = Gfx::IntRect(0, 0, 35, 17).centered_within(m_secondary_color_widget->relative_rect());
116 m_primary_color_widget->set_relative_rect(rect);
117 m_primary_color_widget->set_fill_with_background_color(true);
118
119 m_color_container = add<GUI::Widget>();
120 m_color_container->set_relative_rect(m_secondary_color_widget->relative_rect().right() + 2, 2, 500, 33);
121 m_color_container->set_layout<GUI::VerticalBoxLayout>(GUI::Margins {}, 1);
122
123 auto& top_color_container = m_color_container->add<GUI::Widget>();
124 top_color_container.set_name("top_color_container");
125 top_color_container.set_layout<GUI::HorizontalBoxLayout>(GUI::Margins {}, 1);
126
127 auto& bottom_color_container = m_color_container->add<GUI::Widget>();
128 bottom_color_container.set_name("bottom_color_container");
129 bottom_color_container.set_layout<GUI::HorizontalBoxLayout>(GUI::Margins {}, 1);
130
131 auto result = load_palette_path("/res/color-palettes/default.palette");
132 if (result.is_error()) {
133 GUI::MessageBox::show_error(window(), DeprecatedString::formatted("Loading default palette failed: {}", result.error()));
134 display_color_list(fallback_colors());
135
136 return;
137 }
138
139 display_color_list(result.value());
140}
141
142void PaletteWidget::set_image_editor(ImageEditor* editor)
143{
144 m_editor = editor;
145 if (!m_editor)
146 return;
147
148 set_primary_color(editor->primary_color());
149 set_secondary_color(editor->secondary_color());
150}
151
152void PaletteWidget::set_primary_color(Color color)
153{
154 if (m_editor)
155 m_editor->set_primary_color(color);
156 m_primary_color_widget->set_background_color(color);
157}
158
159void PaletteWidget::set_secondary_color(Color color)
160{
161 if (m_editor)
162 m_editor->set_secondary_color(color);
163 m_secondary_color_widget->set_background_color(color);
164}
165
166void PaletteWidget::display_color_list(Vector<Color> const& colors)
167{
168 int colors_to_add = colors.size();
169 if (colors_to_add == 0) {
170 dbgln("Empty color list given. Using fallback colors.");
171 display_color_list(fallback_colors());
172 return;
173 }
174
175 auto& top_color_container = *m_color_container->find_descendant_of_type_named<GUI::Widget>("top_color_container");
176 top_color_container.remove_all_children();
177
178 auto& bottom_color_container = *m_color_container->find_descendant_of_type_named<GUI::Widget>("bottom_color_container");
179 bottom_color_container.remove_all_children();
180
181 auto add_color_widget = [&](GUI::Widget& container, Color color) {
182 auto& color_widget = container.add<ColorWidget>(color, *this);
183 color_widget.set_fill_with_background_color(true);
184 color_widget.set_fixed_size(16, 16);
185 auto pal = color_widget.palette();
186 pal.set_color(ColorRole::Background, color);
187 color_widget.set_palette(pal);
188 };
189
190 int colors_per_row = ceil(colors_to_add / 2);
191 int number_of_added_colors = 0;
192 for (auto& color : colors) {
193 if (number_of_added_colors < colors_per_row)
194 add_color_widget(top_color_container, color);
195 else
196 add_color_widget(bottom_color_container, color);
197
198 ++number_of_added_colors;
199 }
200}
201
202Vector<Color> PaletteWidget::colors()
203{
204 Vector<Color> colors;
205
206 for (auto& color_container : m_color_container->child_widgets()) {
207 color_container.for_each_child_of_type<ColorWidget>([&](auto& color_widget) {
208 colors.append(color_widget.color());
209 return IterationDecision::Continue;
210 });
211 }
212
213 return colors;
214}
215
216ErrorOr<Vector<Color>> PaletteWidget::load_palette_file(NonnullOwnPtr<Core::File> file)
217{
218 Vector<Color> palette;
219 Array<u8, PAGE_SIZE> buffer;
220 auto buffered_file = TRY(Core::BufferedFile::create(move(file)));
221
222 while (TRY(buffered_file->can_read_line())) {
223 auto line = TRY(buffered_file->read_line(buffer));
224 if (line.is_whitespace())
225 continue;
226
227 auto color = Color::from_string(line);
228 if (!color.has_value()) {
229 dbgln("Could not parse \"{}\" as a color", line);
230 continue;
231 }
232
233 palette.append(color.value());
234 }
235
236 if (palette.is_empty())
237 return Error::from_string_literal("The palette file did not contain any usable colors");
238
239 return palette;
240}
241
242ErrorOr<Vector<Color>> PaletteWidget::load_palette_path(DeprecatedString const& file_path)
243{
244 auto file = TRY(Core::File::open(file_path, Core::File::OpenMode::Read));
245 return load_palette_file(move(file));
246}
247
248ErrorOr<void> PaletteWidget::save_palette_file(Vector<Color> palette, NonnullOwnPtr<Core::File> file)
249{
250 for (auto& color : palette) {
251 TRY(file->write_until_depleted(color.to_deprecated_string_without_alpha().bytes()));
252 TRY(file->write_until_depleted({ "\n", 1 }));
253 }
254 return {};
255}
256
257Vector<Color> PaletteWidget::fallback_colors()
258{
259 Vector<Color> fallback_colors;
260
261 fallback_colors.append(Color::from_rgb(0x000000));
262 fallback_colors.append(Color::from_rgb(0xffffff));
263
264 return fallback_colors;
265}
266
267}