1 | // Aseprite |
2 | // Copyright (C) 2018-2022 Igara Studio S.A. |
3 | // Copyright (C) 2001-2018 David Capello |
4 | // |
5 | // This program is distributed under the terms of |
6 | // the End-User License Agreement for Aseprite. |
7 | |
8 | #define PAL_TRACE(...) // TRACEARGS |
9 | |
10 | #ifdef HAVE_CONFIG_H |
11 | #include "config.h" |
12 | #endif |
13 | |
14 | #include "app/app.h" |
15 | #include "app/color.h" |
16 | #include "app/color_utils.h" |
17 | #include "app/commands/commands.h" |
18 | #include "app/modules/editors.h" |
19 | #include "app/modules/gfx.h" |
20 | #include "app/modules/gui.h" |
21 | #include "app/modules/palettes.h" |
22 | #include "app/site.h" |
23 | #include "app/ui/editor/editor.h" |
24 | #include "app/ui/palette_view.h" |
25 | #include "app/ui/skin/skin_theme.h" |
26 | #include "app/ui/status_bar.h" |
27 | #include "app/ui_context.h" |
28 | #include "app/util/clipboard.h" |
29 | #include "app/util/conversion_to_surface.h" |
30 | #include "app/util/pal_ops.h" |
31 | #include "base/convert_to.h" |
32 | #include "doc/image.h" |
33 | #include "doc/layer_tilemap.h" |
34 | #include "doc/palette.h" |
35 | #include "doc/remap.h" |
36 | #include "doc/tileset.h" |
37 | #include "fmt/format.h" |
38 | #include "gfx/color.h" |
39 | #include "gfx/point.h" |
40 | #include "os/font.h" |
41 | #include "os/surface.h" |
42 | #include "os/surface.h" |
43 | #include "os/system.h" |
44 | #include "ui/graphics.h" |
45 | #include "ui/manager.h" |
46 | #include "ui/message.h" |
47 | #include "ui/paint_event.h" |
48 | #include "ui/size_hint_event.h" |
49 | #include "ui/system.h" |
50 | #include "ui/theme.h" |
51 | #include "ui/view.h" |
52 | #include "ui/widget.h" |
53 | |
54 | #include <algorithm> |
55 | #include <cstdlib> |
56 | #include <cstring> |
57 | #include <set> |
58 | |
59 | namespace app { |
60 | |
61 | using namespace ui; |
62 | using namespace app::skin; |
63 | |
64 | // Interface used to adapt the PaletteView widget to see tilesets too. |
65 | class AbstractPaletteViewAdapter { |
66 | public: |
67 | virtual ~AbstractPaletteViewAdapter() { } |
68 | virtual int size() const = 0; |
69 | virtual void paletteChange(doc::PalettePicks& picks) = 0; |
70 | virtual void activeSiteChange(const Site& site, doc::PalettePicks& picks) = 0; |
71 | virtual void clearSelection(PaletteView* paletteView, |
72 | doc::PalettePicks& picks) = 0; |
73 | virtual void selectIndex(PaletteView* paletteView, |
74 | int index, ui::MouseButton button) = 0; |
75 | virtual void resizePalette(PaletteView* paletteView, |
76 | int newSize) = 0; |
77 | virtual void dropColors(PaletteView* paletteView, |
78 | doc::PalettePicks& picks, |
79 | int& currentEntry, |
80 | const int beforeIndex, |
81 | const bool isCopy) = 0; |
82 | virtual void showEntryInStatusBar(StatusBar* statusBar, int index) = 0; |
83 | virtual void showDragInfoInStatusBar(StatusBar* statusBar, bool copy, int destIndex, int newSize) = 0; |
84 | virtual void showResizeInfoInStatusBar(StatusBar* statusBar, int newSize) = 0; |
85 | virtual void drawEntry(ui::Graphics* g, |
86 | SkinTheme* theme, |
87 | const int palIdx, |
88 | const int offIdx, |
89 | const int childSpacing, |
90 | gfx::Rect& box, |
91 | gfx::Color& negColor) = 0; |
92 | virtual doc::Tileset* tileset() const { return nullptr; } |
93 | }; |
94 | |
95 | // This default adapter uses the default behavior to use the |
96 | // PaletteView as just a doc::Palette view. |
97 | class PaletteViewAdapter : public AbstractPaletteViewAdapter { |
98 | public: |
99 | int size() const override { return palette()->size(); } |
100 | void paletteChange(doc::PalettePicks& picks) override { |
101 | picks.resize(palette()->size()); |
102 | } |
103 | void activeSiteChange(const Site& site, doc::PalettePicks& picks) override { |
104 | // Do nothing |
105 | } |
106 | void clearSelection(PaletteView* paletteView, |
107 | doc::PalettePicks& picks) override { |
108 | Palette palette(*this->palette()); |
109 | Palette newPalette(palette); |
110 | newPalette.resize(std::max(1, newPalette.size() - picks.picks())); |
111 | |
112 | Remap remap = create_remap_to_move_picks(picks, palette.size()); |
113 | for (int i=0; i<palette.size(); ++i) { |
114 | if (!picks[i]) |
115 | newPalette.setEntry(remap[i], palette.getEntry(i)); |
116 | } |
117 | |
118 | paletteView->setNewPalette(&palette, &newPalette, |
119 | PaletteViewModification::CLEAR); |
120 | } |
121 | void selectIndex(PaletteView* paletteView, |
122 | int index, ui::MouseButton button) override { |
123 | // Emit signal |
124 | if (paletteView->delegate()) |
125 | paletteView->delegate()->onPaletteViewIndexChange(index, button); |
126 | } |
127 | void resizePalette(PaletteView* paletteView, |
128 | int newSize) override { |
129 | Palette newPalette(*paletteView->currentPalette()); |
130 | newPalette.resize(newSize); |
131 | paletteView->setNewPalette(paletteView->currentPalette(), |
132 | &newPalette, |
133 | PaletteViewModification::RESIZE); |
134 | } |
135 | void dropColors(PaletteView* paletteView, |
136 | doc::PalettePicks& picks, |
137 | int& currentEntry, |
138 | const int beforeIndex, |
139 | const bool isCopy) override { |
140 | Palette palette(*paletteView->currentPalette()); |
141 | Palette newPalette(palette); |
142 | move_or_copy_palette_colors( |
143 | palette, |
144 | newPalette, |
145 | picks, |
146 | currentEntry, |
147 | beforeIndex, |
148 | isCopy); |
149 | paletteView->setNewPalette(&palette, &newPalette, |
150 | PaletteViewModification::DRAGANDDROP); |
151 | } |
152 | void showEntryInStatusBar(StatusBar* statusBar, int index) override { |
153 | statusBar->showColor(0, app::Color::fromIndex(index)); |
154 | } |
155 | void showDragInfoInStatusBar(StatusBar* statusBar, bool copy, int destIndex, int newSize) override { |
156 | statusBar->setStatusText( |
157 | 0, fmt::format("{} to {} - New Palette Size {}" , |
158 | (copy ? "Copy" : "Move" ), |
159 | destIndex, newSize)); |
160 | } |
161 | void showResizeInfoInStatusBar(StatusBar* statusBar, int newSize) override { |
162 | statusBar->setStatusText( |
163 | 0, fmt::format("New Palette Size {}" , newSize)); |
164 | } |
165 | void drawEntry(ui::Graphics* g, |
166 | SkinTheme* theme, |
167 | const int palIdx, |
168 | const int offIdx, |
169 | const int childSpacing, |
170 | gfx::Rect& box, |
171 | gfx::Color& negColor) override { |
172 | doc::color_t palColor = |
173 | (palIdx < palette()->size() ? palette()->getEntry(palIdx): |
174 | rgba(0, 0, 0, 255)); |
175 | app::Color appColor = app::Color::fromRgb( |
176 | rgba_getr(palColor), |
177 | rgba_getg(palColor), |
178 | rgba_getb(palColor), |
179 | rgba_geta(palColor)); |
180 | |
181 | if (childSpacing > 0) { |
182 | gfx::Color color = theme->colors.paletteEntriesSeparator(); |
183 | g->fillRect(color, gfx::Rect(box).enlarge(childSpacing)); |
184 | } |
185 | draw_color(g, box, appColor, doc::ColorMode::RGB); |
186 | |
187 | const gfx::Color gfxColor = gfx::rgba( |
188 | rgba_getr(palColor), |
189 | rgba_getg(palColor), |
190 | rgba_getb(palColor), |
191 | rgba_geta(palColor)); |
192 | negColor = color_utils::blackandwhite_neg(gfxColor); |
193 | } |
194 | private: |
195 | doc::Palette* palette() const { |
196 | return get_current_palette(); |
197 | } |
198 | }; |
199 | |
200 | // This adapter makes it possible to use a PaletteView to edit a |
201 | // doc::Tileset. |
202 | class TilesetViewAdapter : public AbstractPaletteViewAdapter { |
203 | public: |
204 | int size() const override { |
205 | if (auto t = tileset()) |
206 | return t->size(); |
207 | else |
208 | return 0; |
209 | } |
210 | void paletteChange(doc::PalettePicks& picks) override { |
211 | // Do nothing |
212 | } |
213 | void activeSiteChange(const Site& site, doc::PalettePicks& picks) override { |
214 | if (auto tileset = this->tileset()) |
215 | picks.resize(tileset->size()); |
216 | else |
217 | picks.clear(); |
218 | } |
219 | void clearSelection(PaletteView* paletteView, |
220 | doc::PalettePicks& picks) override { |
221 | // Cannot delete the empty tile (index 0) |
222 | int i = picks.firstPick(); |
223 | if (i == doc::notile) { |
224 | picks[i] = false; |
225 | if (!picks.picks()) { |
226 | // Cannot remove empty tile |
227 | StatusBar::instance()->showTip(1000, "Cannot delete the empty tile" ); |
228 | return; |
229 | } |
230 | } |
231 | |
232 | paletteView->delegate()->onTilesViewClearTiles(picks); |
233 | } |
234 | void selectIndex(PaletteView* paletteView, |
235 | int index, ui::MouseButton button) override { |
236 | // Emit signal |
237 | if (paletteView->delegate()) |
238 | paletteView->delegate()->onTilesViewIndexChange(index, button); |
239 | } |
240 | void resizePalette(PaletteView* paletteView, |
241 | int newSize) override { |
242 | paletteView->delegate()->onTilesViewResize(newSize); |
243 | } |
244 | void dropColors(PaletteView* paletteView, |
245 | doc::PalettePicks& picks, |
246 | int& currentEntry, |
247 | const int _beforeIndex, |
248 | const bool isCopy) override { |
249 | PAL_TRACE("dropColors" ); |
250 | |
251 | doc::Tileset* tileset = this->tileset(); |
252 | ASSERT(tileset); |
253 | if (!tileset) |
254 | return; |
255 | |
256 | // Important: we create a copy because if we make the tileset |
257 | // bigger dropping tiles outside the tileset range, the tileset |
258 | // will be made bigger (see cmd::AddTile() inside |
259 | // move_tiles_in_tileset() function), a |
260 | // Doc::notifyTilesetChanged() will be generated, a |
261 | // ColorBar::onTilesetChanged() called, and finally we'll receive a |
262 | // PaletteView::deselect() that will clear the whole picks. |
263 | auto newPicks = picks; |
264 | int beforeIndex = _beforeIndex; |
265 | |
266 | // We cannot move the empty tile (index 0) no any place |
267 | if (beforeIndex == 0) |
268 | ++beforeIndex; |
269 | if (!isCopy && newPicks.size() > 0 && newPicks[0]) |
270 | newPicks[0] = false; |
271 | if (!newPicks.picks()) { |
272 | // Cannot move empty tile |
273 | StatusBar::instance()->showTip(1000, "Cannot move the empty tile" ); |
274 | return; |
275 | } |
276 | |
277 | paletteView->delegate()->onTilesViewDragAndDrop( |
278 | tileset, newPicks, currentEntry, beforeIndex, isCopy); |
279 | |
280 | // Copy the new picks |
281 | picks = newPicks; |
282 | } |
283 | void showEntryInStatusBar(StatusBar* statusBar, int index) override { |
284 | statusBar->showTile(0, doc::tile(index, 0)); |
285 | } |
286 | void showDragInfoInStatusBar(StatusBar* statusBar, bool copy, int destIndex, int newSize) override { |
287 | statusBar->setStatusText( |
288 | 0, fmt::format("{} to {} - New Tileset Size {}" , |
289 | (copy ? "Copy" : "Move" ), |
290 | destIndex, newSize)); |
291 | } |
292 | void showResizeInfoInStatusBar(StatusBar* statusBar, int newSize) override { |
293 | statusBar->setStatusText( |
294 | 0, fmt::format("New Tileset Size {}" , newSize)); |
295 | } |
296 | void drawEntry(ui::Graphics* g, |
297 | SkinTheme* theme, |
298 | const int palIdx, |
299 | const int offIdx, |
300 | const int childSpacing, |
301 | gfx::Rect& box, |
302 | gfx::Color& negColor) override { |
303 | if (childSpacing > 0) { |
304 | gfx::Color color = theme->colors.paletteEntriesSeparator(); |
305 | g->fillRect(color, gfx::Rect(box).enlarge(childSpacing)); |
306 | } |
307 | draw_color(g, box, app::Color::fromMask(), doc::ColorMode::RGB); |
308 | |
309 | doc::ImageRef tileImage; |
310 | if (auto t = this->tileset()) |
311 | tileImage = t->get(palIdx); |
312 | if (tileImage) { |
313 | int w = tileImage->width(); |
314 | int h = tileImage->height(); |
315 | os::SurfaceRef surface = os::instance()->makeRgbaSurface(w, h); |
316 | convert_image_to_surface(tileImage.get(), get_current_palette(), |
317 | surface.get(), 0, 0, 0, 0, w, h); |
318 | |
319 | ui::Paint paint; |
320 | paint.blendMode(os::BlendMode::SrcOver); |
321 | |
322 | os::Sampling sampling; |
323 | if (w > box.w && h > box.h) { |
324 | sampling = os::Sampling(os::Sampling::Filter::Linear, |
325 | os::Sampling::Mipmap::Nearest); |
326 | } |
327 | |
328 | g->drawSurface(surface.get(), gfx::Rect(0, 0, w, h), box, |
329 | sampling, &paint); |
330 | } |
331 | negColor = gfx::rgba(255, 255, 255); |
332 | } |
333 | doc::Tileset* tileset() const override { |
334 | Site site = App::instance()->context()->activeSite(); |
335 | if (site.layer() && |
336 | site.layer()->isTilemap()) { |
337 | return static_cast<LayerTilemap*>(site.layer())->tileset(); |
338 | } |
339 | else |
340 | return nullptr; |
341 | } |
342 | private: |
343 | }; |
344 | |
345 | PaletteView::PaletteView(bool editable, PaletteViewStyle style, PaletteViewDelegate* delegate, int boxsize) |
346 | : Widget(kGenericWidget) |
347 | , m_state(State::WAITING) |
348 | , m_editable(editable) |
349 | , m_style(style) |
350 | , m_delegate(delegate) |
351 | , m_adapter(isTiles() ? (AbstractPaletteViewAdapter*)new TilesetViewAdapter: |
352 | (AbstractPaletteViewAdapter*)new PaletteViewAdapter) |
353 | , m_columns(16) |
354 | , m_boxsize(boxsize) |
355 | , m_currentEntry(-1) |
356 | , m_rangeAnchor(-1) |
357 | , m_isUpdatingColumns(false) |
358 | , m_hot(Hit::NONE) |
359 | , m_copy(false) |
360 | { |
361 | setFocusStop(true); |
362 | setDoubleBuffered(true); |
363 | |
364 | m_palConn = App::instance()->PaletteChange.connect(&PaletteView::onAppPaletteChange, this); |
365 | m_csConn = App::instance()->ColorSpaceChange.connect( |
366 | [this]{ invalidate(); }); |
367 | |
368 | { |
369 | auto& entriesSep = Preferences::instance().colorBar.entriesSeparator; |
370 | m_withSeparator = entriesSep(); |
371 | m_sepConn = entriesSep.AfterChange.connect( |
372 | [this, &entriesSep](bool newValue) { |
373 | m_withSeparator = entriesSep(); |
374 | updateBorderAndChildSpacing(); |
375 | }); |
376 | } |
377 | |
378 | if (isTiles()) |
379 | UIContext::instance()->add_observer(this); |
380 | |
381 | InitTheme.connect( |
382 | [this]{ |
383 | updateBorderAndChildSpacing(); |
384 | }); |
385 | initTheme(); |
386 | } |
387 | |
388 | PaletteView::~PaletteView() |
389 | { |
390 | if (isTiles()) |
391 | UIContext::instance()->remove_observer(this); |
392 | } |
393 | |
394 | void PaletteView::setColumns(int columns) |
395 | { |
396 | int old_columns = m_columns; |
397 | m_columns = columns; |
398 | |
399 | if (m_columns != old_columns) { |
400 | View* view = View::getView(this); |
401 | if (view) |
402 | view->updateView(); |
403 | |
404 | invalidate(); |
405 | } |
406 | } |
407 | |
408 | void PaletteView::deselect() |
409 | { |
410 | bool invalidate = (m_selectedEntries.picks() > 0); |
411 | |
412 | m_selectedEntries.resize(m_adapter->size()); |
413 | m_selectedEntries.clear(); |
414 | |
415 | if (invalidate) |
416 | this->invalidate(); |
417 | } |
418 | |
419 | void PaletteView::selectColor(int index) |
420 | { |
421 | if (index < 0 || index >= m_adapter->size()) |
422 | return; |
423 | |
424 | if (m_currentEntry != index || !m_selectedEntries[index]) { |
425 | m_currentEntry = index; |
426 | m_rangeAnchor = index; |
427 | |
428 | update_scroll(m_currentEntry); |
429 | invalidate(); |
430 | } |
431 | } |
432 | |
433 | void PaletteView::selectExactMatchColor(const app::Color& color) |
434 | { |
435 | int index = findExactIndex(color); |
436 | if (index >= 0) |
437 | selectColor(index); |
438 | } |
439 | |
440 | void PaletteView::selectRange(int index1, int index2) |
441 | { |
442 | m_rangeAnchor = index1; |
443 | m_currentEntry = index2; |
444 | |
445 | std::fill(m_selectedEntries.begin()+std::min(index1, index2), |
446 | m_selectedEntries.begin()+std::max(index1, index2)+1, true); |
447 | |
448 | update_scroll(index2); |
449 | invalidate(); |
450 | } |
451 | |
452 | int PaletteView::getSelectedEntry() const |
453 | { |
454 | return m_currentEntry; |
455 | } |
456 | |
457 | bool PaletteView::getSelectedRange(int& index1, int& index2) const |
458 | { |
459 | int i, i2, j; |
460 | |
461 | // Find the first selected entry |
462 | for (i=0; i<(int)m_selectedEntries.size(); ++i) |
463 | if (m_selectedEntries[i]) |
464 | break; |
465 | |
466 | // Find the first unselected entry after i |
467 | for (i2=i+1; i2<(int)m_selectedEntries.size(); ++i2) |
468 | if (!m_selectedEntries[i2]) |
469 | break; |
470 | |
471 | // Find the last selected entry |
472 | for (j=m_selectedEntries.size()-1; j>=0; --j) |
473 | if (m_selectedEntries[j]) |
474 | break; |
475 | |
476 | if (j-i+1 == i2-i) { |
477 | index1 = i; |
478 | index2 = j; |
479 | return true; |
480 | } |
481 | else |
482 | return false; |
483 | } |
484 | |
485 | void PaletteView::getSelectedEntries(PalettePicks& entries) const |
486 | { |
487 | entries = m_selectedEntries; |
488 | } |
489 | |
490 | int PaletteView::getSelectedEntriesCount() const |
491 | { |
492 | return m_selectedEntries.picks(); |
493 | } |
494 | |
495 | void PaletteView::setSelectedEntries(const doc::PalettePicks& entries) |
496 | { |
497 | m_selectedEntries = entries; |
498 | m_selectedEntries.resize(m_adapter->size()); |
499 | m_currentEntry = m_selectedEntries.firstPick(); |
500 | |
501 | if (entries.picks() > 0) |
502 | requestFocus(); |
503 | |
504 | invalidate(); |
505 | } |
506 | |
507 | doc::Tileset* PaletteView::tileset() const |
508 | { |
509 | return m_adapter->tileset(); |
510 | } |
511 | |
512 | app::Color PaletteView::getColorByPosition(const gfx::Point& pos) |
513 | { |
514 | gfx::Point relPos = pos - bounds().origin(); |
515 | for (int i=0; i<m_adapter->size(); ++i) { |
516 | auto box = getPaletteEntryBounds(i); |
517 | box.inflate(childSpacing()); |
518 | if (box.contains(relPos)) |
519 | return app::Color::fromIndex(i); |
520 | } |
521 | return app::Color::fromMask(); |
522 | } |
523 | |
524 | doc::tile_t PaletteView::getTileByPosition(const gfx::Point& pos) |
525 | { |
526 | gfx::Point relPos = pos - bounds().origin(); |
527 | for (int i=0; i<m_adapter->size(); ++i) { |
528 | auto box = getPaletteEntryBounds(i); |
529 | box.inflate(childSpacing()); |
530 | if (box.contains(relPos)) |
531 | return doc::tile(i, 0); |
532 | } |
533 | return doc::notile; |
534 | } |
535 | |
536 | void PaletteView::onActiveSiteChange(const Site& site) |
537 | { |
538 | ASSERT(isTiles()); |
539 | m_adapter->activeSiteChange(site, m_selectedEntries); |
540 | } |
541 | |
542 | int PaletteView::getBoxSize() const |
543 | { |
544 | return int(m_boxsize); |
545 | } |
546 | |
547 | void PaletteView::setBoxSize(double boxsize) |
548 | { |
549 | if (isTiles()) |
550 | m_boxsize = std::clamp(boxsize, 4.0, 64.0); |
551 | else |
552 | m_boxsize = std::clamp(boxsize, 4.0, 32.0); |
553 | |
554 | if (m_delegate) |
555 | m_delegate->onPaletteViewChangeSize(this, int(m_boxsize)); |
556 | |
557 | View* view = View::getView(this); |
558 | if (view) |
559 | view->layout(); |
560 | } |
561 | |
562 | void PaletteView::clearSelection() |
563 | { |
564 | if (!m_selectedEntries.picks()) |
565 | return; |
566 | |
567 | m_adapter->clearSelection(this, m_selectedEntries); |
568 | |
569 | m_currentEntry = m_selectedEntries.firstPick(); |
570 | m_selectedEntries.clear(); |
571 | stopMarchingAnts(); |
572 | } |
573 | |
574 | void PaletteView::cutToClipboard() |
575 | { |
576 | if (!m_selectedEntries.picks()) |
577 | return; |
578 | |
579 | Clipboard::instance()->copyPalette(currentPalette(), m_selectedEntries); |
580 | |
581 | clearSelection(); |
582 | } |
583 | |
584 | void PaletteView::copyToClipboard() |
585 | { |
586 | if (!m_selectedEntries.picks()) |
587 | return; |
588 | |
589 | Clipboard::instance()->copyPalette(currentPalette(), m_selectedEntries); |
590 | |
591 | startMarchingAnts(); |
592 | invalidate(); |
593 | } |
594 | |
595 | void PaletteView::pasteFromClipboard() |
596 | { |
597 | auto clipboard = Clipboard::instance(); |
598 | if (clipboard->format() == ClipboardFormat::PaletteEntries) { |
599 | if (m_delegate) |
600 | m_delegate->onPaletteViewPasteColors( |
601 | clipboard->getPalette(), |
602 | clipboard->getPalettePicks(), |
603 | m_selectedEntries); |
604 | |
605 | // We just hide the marching ants, the user can paste multiple |
606 | // times. |
607 | stopMarchingAnts(); |
608 | invalidate(); |
609 | } |
610 | } |
611 | |
612 | void PaletteView::discardClipboardSelection() |
613 | { |
614 | if (isMarchingAntsRunning()) { |
615 | stopMarchingAnts(); |
616 | invalidate(); |
617 | } |
618 | } |
619 | |
620 | bool PaletteView::onProcessMessage(Message* msg) |
621 | { |
622 | switch (msg->type()) { |
623 | |
624 | case kFocusEnterMessage: |
625 | FocusOrClick(msg); |
626 | break; |
627 | |
628 | case kKeyDownMessage: |
629 | case kKeyUpMessage: |
630 | case kMouseEnterMessage: |
631 | if (hasMouse()) |
632 | updateCopyFlag(msg); |
633 | break; |
634 | |
635 | case kMouseDownMessage: |
636 | switch (m_hot.part) { |
637 | |
638 | case Hit::COLOR: |
639 | case Hit::POSSIBLE_COLOR: |
640 | // Clicking outside the palette range will deselect |
641 | if (m_hot.color >= m_adapter->size()) { |
642 | deselect(); |
643 | break; |
644 | } |
645 | |
646 | m_state = State::SELECTING_COLOR; |
647 | |
648 | // As we can ctrl+click color bar + timeline, now we have to |
649 | // re-prioritize the color bar on each click. |
650 | FocusOrClick(msg); |
651 | break; |
652 | |
653 | case Hit::OUTLINE: |
654 | m_state = State::DRAGGING_OUTLINE; |
655 | break; |
656 | |
657 | case Hit::RESIZE_HANDLE: |
658 | m_state = State::RESIZING_PALETTE; |
659 | break; |
660 | } |
661 | |
662 | captureMouse(); |
663 | [[fallthrough]]; |
664 | |
665 | case kMouseMoveMessage: { |
666 | MouseMessage* mouseMsg = static_cast<MouseMessage*>(msg); |
667 | |
668 | if ((m_state == State::SELECTING_COLOR) && |
669 | (m_hot.part == Hit::COLOR || |
670 | m_hot.part == Hit::POSSIBLE_COLOR)) { |
671 | int idx = m_hot.color; |
672 | idx = std::clamp(idx, 0, std::max(0, m_adapter->size()-1)); |
673 | |
674 | const MouseButton button = mouseMsg->button(); |
675 | |
676 | if (hasCapture() && |
677 | (idx >= 0 && idx < m_adapter->size()) && |
678 | ((idx != m_currentEntry) || |
679 | (msg->type() == kMouseDownMessage) || |
680 | (button == kButtonMiddle))) { |
681 | if (button != kButtonMiddle) { |
682 | if (!msg->ctrlPressed() && !msg->shiftPressed()) |
683 | deselect(); |
684 | |
685 | if (msg->type() == kMouseMoveMessage) |
686 | selectRange(m_rangeAnchor, idx); |
687 | else { |
688 | selectColor(idx); |
689 | m_selectedEntries[idx] = true; |
690 | } |
691 | } |
692 | |
693 | m_adapter->selectIndex(this, idx, button); |
694 | } |
695 | } |
696 | |
697 | if (m_state == State::DRAGGING_OUTLINE && |
698 | m_hot.part == Hit::COLOR) { |
699 | update_scroll(m_hot.color); |
700 | } |
701 | |
702 | if (hasCapture()) |
703 | return true; |
704 | |
705 | break; |
706 | } |
707 | |
708 | case kMouseUpMessage: |
709 | if (hasCapture()) { |
710 | releaseMouse(); |
711 | |
712 | switch (m_state) { |
713 | |
714 | case State::DRAGGING_OUTLINE: |
715 | if (m_hot.part == Hit::COLOR || |
716 | m_hot.part == Hit::POSSIBLE_COLOR) { |
717 | int i = m_hot.color; |
718 | if (!m_copy && i > m_selectedEntries.firstPick()) |
719 | i += m_selectedEntries.picks(); |
720 | dropColors(i); |
721 | } |
722 | break; |
723 | |
724 | case State::RESIZING_PALETTE: |
725 | if (m_hot.part == Hit::COLOR || |
726 | m_hot.part == Hit::POSSIBLE_COLOR) { |
727 | int newSize = std::max(1, m_hot.color); |
728 | m_adapter->resizePalette(this, newSize); |
729 | m_selectedEntries.resize(newSize); |
730 | } |
731 | break; |
732 | } |
733 | |
734 | m_state = State::WAITING; |
735 | setStatusBar(); |
736 | invalidate(); |
737 | } |
738 | return true; |
739 | |
740 | case kMouseWheelMessage: { |
741 | View* view = View::getView(this); |
742 | if (!view) |
743 | break; |
744 | |
745 | gfx::Point delta = static_cast<MouseMessage*>(msg)->wheelDelta(); |
746 | |
747 | if (msg->onlyCtrlPressed() || |
748 | msg->onlyCmdPressed()) { |
749 | int z = delta.x - delta.y; |
750 | setBoxSize(m_boxsize + z); |
751 | } |
752 | else { |
753 | gfx::Point scroll = view->viewScroll(); |
754 | |
755 | if (static_cast<MouseMessage*>(msg)->preciseWheel()) |
756 | scroll += delta; |
757 | else |
758 | scroll += delta * 3 * boxSizePx(); |
759 | |
760 | view->setViewScroll(scroll); |
761 | } |
762 | break; |
763 | } |
764 | |
765 | case kMouseLeaveMessage: |
766 | m_hot = Hit(Hit::NONE); |
767 | setStatusBar(); |
768 | invalidate(); |
769 | break; |
770 | |
771 | case kSetCursorMessage: { |
772 | MouseMessage* mouseMsg = static_cast<MouseMessage*>(msg); |
773 | Hit hit = hitTest(mouseMsg->position() - bounds().origin()); |
774 | if (hit != m_hot) { |
775 | // Redraw only when we put the mouse in other part of the |
776 | // widget (e.g. if we move from color to color, we don't want |
777 | // to redraw the whole widget if we're on WAITING state). |
778 | if ((m_state == State::WAITING && hit.part != m_hot.part) || |
779 | (m_state != State::WAITING && hit != m_hot)) { |
780 | invalidate(); |
781 | } |
782 | m_hot = hit; |
783 | setStatusBar(); |
784 | } |
785 | setCursor(); |
786 | return true; |
787 | } |
788 | |
789 | case kTouchMagnifyMessage: { |
790 | setBoxSize(m_boxsize + |
791 | m_boxsize * static_cast<ui::TouchMessage*>(msg)->magnification()); |
792 | break; |
793 | } |
794 | |
795 | } |
796 | |
797 | return Widget::onProcessMessage(msg); |
798 | } |
799 | |
800 | void PaletteView::onPaint(ui::PaintEvent& ev) |
801 | { |
802 | auto theme = SkinTheme::get(this); |
803 | const int outlineWidth = theme->dimensions.paletteOutlineWidth(); |
804 | ui::Graphics* g = ev.graphics(); |
805 | gfx::Rect bounds = clientBounds(); |
806 | Palette* palette = currentPalette(); |
807 | int fgIndex = -1; |
808 | int bgIndex = -1; |
809 | int transparentIndex = -1; |
810 | const bool hotColor = (m_hot.part == Hit::COLOR || |
811 | m_hot.part == Hit::POSSIBLE_COLOR); |
812 | const bool dragging = (m_state == State::DRAGGING_OUTLINE && hotColor); |
813 | const bool resizing = (m_state == State::RESIZING_PALETTE && hotColor); |
814 | |
815 | if (m_delegate) { |
816 | switch (m_style) { |
817 | |
818 | case FgBgColors: |
819 | fgIndex = findExactIndex(m_delegate->onPaletteViewGetForegroundIndex()); |
820 | bgIndex = findExactIndex(m_delegate->onPaletteViewGetBackgroundIndex()); |
821 | |
822 | if (current_editor && current_editor->sprite()->pixelFormat() == IMAGE_INDEXED) |
823 | transparentIndex = current_editor->sprite()->transparentColor(); |
824 | break; |
825 | |
826 | case FgBgTiles: |
827 | fgIndex = m_delegate->onPaletteViewGetForegroundTile(); |
828 | bgIndex = m_delegate->onPaletteViewGetBackgroundTile(); |
829 | break; |
830 | } |
831 | } |
832 | |
833 | g->fillRect(theme->colors.editorFace(), bounds); |
834 | |
835 | // Draw palette/tileset entries |
836 | int picksCount = m_selectedEntries.picks(); |
837 | int idxOffset = 0; |
838 | int boxOffset = 0; |
839 | int palSize = m_adapter->size(); |
840 | if (dragging && !m_copy) palSize -= picksCount; |
841 | if (resizing) palSize = m_hot.color; |
842 | |
843 | for (int i=0; i<palSize; ++i) { |
844 | if (dragging) { |
845 | if (!m_copy) { |
846 | while (i+idxOffset < m_selectedEntries.size() && |
847 | m_selectedEntries[i+idxOffset]) |
848 | ++idxOffset; |
849 | } |
850 | if (!boxOffset && m_hot.color == i) { |
851 | boxOffset += picksCount; |
852 | } |
853 | } |
854 | |
855 | gfx::Rect box = getPaletteEntryBounds(i + boxOffset); |
856 | gfx::Color negColor; |
857 | m_adapter->drawEntry(g, theme, i + idxOffset, i + boxOffset, |
858 | childSpacing(), box, negColor); |
859 | const int boxsize = boxSizePx(); |
860 | const int scale = guiscale(); |
861 | |
862 | switch (m_style) { |
863 | |
864 | case SelectOneColor: |
865 | if (m_currentEntry == i) |
866 | g->fillRect(negColor, gfx::Rect(box.center(), gfx::Size(scale, scale))); |
867 | break; |
868 | |
869 | case FgBgColors: |
870 | case FgBgTiles: |
871 | if (!m_delegate || m_delegate->onIsPaletteViewActive(this)) { |
872 | if (fgIndex == i) { |
873 | for (int i=0; i<int(boxsize/2); i += scale) { |
874 | g->fillRect(negColor, |
875 | gfx::Rect(box.x, box.y+i, int(boxsize/2)-i, scale)); |
876 | } |
877 | } |
878 | |
879 | if (bgIndex == i) { |
880 | for (int i=0; i<int(boxsize/4); i += scale) { |
881 | g->fillRect(negColor, |
882 | gfx::Rect(box.x+box.w-(i+scale), |
883 | box.y+box.h-int(boxsize/4)+i, |
884 | i+scale, scale)); |
885 | } |
886 | } |
887 | |
888 | if (transparentIndex == i) |
889 | g->fillRect(negColor, gfx::Rect(box.center(), gfx::Size(scale, scale))); |
890 | } |
891 | break; |
892 | } |
893 | } |
894 | |
895 | // Handle to resize palette |
896 | |
897 | if (m_editable && !dragging) { |
898 | os::Surface* handle = theme->parts.palResize()->bitmap(0); |
899 | gfx::Rect box = getPaletteEntryBounds(palSize); |
900 | g->drawRgbaSurface(handle, |
901 | box.x+box.w/2-handle->width()/2, |
902 | box.y+box.h/2-handle->height()/2); |
903 | } |
904 | |
905 | // Draw selected entries |
906 | |
907 | PaintWidgetPartInfo info; |
908 | if (m_hot.part == Hit::OUTLINE) |
909 | info.styleFlags |= ui::Style::Layer::kMouse; |
910 | |
911 | PalettePicks dragPicks; |
912 | int j = 0; |
913 | if (dragging) { |
914 | dragPicks.resize(m_hot.color+picksCount); |
915 | std::fill(dragPicks.begin()+m_hot.color, dragPicks.end(), true); |
916 | } |
917 | PalettePicks& picks = (dragging ? dragPicks: m_selectedEntries); |
918 | |
919 | const int size = m_adapter->size(); |
920 | for (int i=0; i<size; ++i) { |
921 | // TODO why does this fail? |
922 | //ASSERT(i >= 0 && i < m_selectedEntries.size()); |
923 | if (i >= 0 && i < m_selectedEntries.size() && |
924 | !m_selectedEntries[i]) { |
925 | continue; |
926 | } |
927 | |
928 | const int k = (dragging ? m_hot.color+j: i); |
929 | |
930 | gfx::Rect box, clipR; |
931 | getEntryBoundsAndClip(k, picks, outlineWidth, box, clipR); |
932 | |
933 | IntersectClip clip(g, clipR); |
934 | if (clip) { |
935 | // Draw color being dragged + label |
936 | if (dragging) { |
937 | gfx::Rect box2 = getPaletteEntryBounds(k); |
938 | gfx::Color negColor; |
939 | m_adapter->drawEntry(g, theme, i, k, childSpacing(), |
940 | box2, negColor); |
941 | |
942 | os::Font* minifont = theme->getMiniFont(); |
943 | const std::string text = base::convert_to<std::string>(k); |
944 | g->setFont(AddRef(minifont)); |
945 | g->drawText(text, negColor, gfx::ColorNone, |
946 | gfx::Point(box2.x + box2.w/2 - minifont->textLength(text)/2, |
947 | box2.y + box2.h/2 - minifont->height()/2)); |
948 | } |
949 | |
950 | // Draw the selection |
951 | theme->paintWidgetPart( |
952 | g, theme->styles.colorbarSelection(), box, info); |
953 | } |
954 | |
955 | ++j; |
956 | } |
957 | |
958 | // Draw marching ants |
959 | if ((m_state == State::WAITING) && |
960 | (isMarchingAntsRunning()) && |
961 | (Clipboard::instance()->format() == ClipboardFormat::PaletteEntries)) { |
962 | auto clipboard = Clipboard::instance(); |
963 | Palette* clipboardPalette = clipboard->getPalette(); |
964 | const PalettePicks& clipboardPicks = clipboard->getPalettePicks(); |
965 | |
966 | if (clipboardPalette && |
967 | clipboardPalette->countDiff(palette, nullptr, nullptr) == 0) { |
968 | for (int i=0; i<clipboardPicks.size(); ++i) { |
969 | if (!clipboardPicks[i]) |
970 | continue; |
971 | |
972 | gfx::Rect box, clipR; |
973 | getEntryBoundsAndClip(i, clipboardPicks, 1*guiscale(), box, clipR); |
974 | |
975 | IntersectClip clip(g, clipR); |
976 | if (clip) { |
977 | ui::Paint paint; |
978 | paint.style(ui::Paint::Stroke); |
979 | ui::set_checkered_paint_mode(paint, getMarchingAntsOffset(), |
980 | gfx::rgba(0, 0, 0, 255), |
981 | gfx::rgba(255, 255, 255, 255)); |
982 | g->drawRect(box, paint); |
983 | } |
984 | } |
985 | } |
986 | } |
987 | } |
988 | |
989 | void PaletteView::onResize(ui::ResizeEvent& ev) |
990 | { |
991 | if (!m_isUpdatingColumns) { |
992 | m_isUpdatingColumns = true; |
993 | View* view = View::getView(this); |
994 | if (view) { |
995 | int columns = |
996 | (view->viewportBounds().w |
997 | -this->border().width() |
998 | +this->childSpacing()) |
999 | / (boxSizePx() |
1000 | +this->childSpacing()); |
1001 | setColumns(std::max(1, columns)); |
1002 | } |
1003 | m_isUpdatingColumns = false; |
1004 | } |
1005 | |
1006 | Widget::onResize(ev); |
1007 | } |
1008 | |
1009 | void PaletteView::onSizeHint(ui::SizeHintEvent& ev) |
1010 | { |
1011 | div_t d = div(m_adapter->size(), m_columns); |
1012 | int cols = m_columns; |
1013 | int rows = d.quot + ((d.rem)? 1: 0); |
1014 | |
1015 | if (m_editable) { |
1016 | ++rows; |
1017 | } |
1018 | |
1019 | const int boxsize = boxSizePx(); |
1020 | ev.setSizeHint( |
1021 | gfx::Size( |
1022 | border().width() + cols*boxsize + (cols-1)*childSpacing(), |
1023 | border().height() + rows*boxsize + (rows-1)*childSpacing())); |
1024 | } |
1025 | |
1026 | void PaletteView::onDrawMarchingAnts() |
1027 | { |
1028 | invalidate(); |
1029 | } |
1030 | |
1031 | void PaletteView::update_scroll(int color) |
1032 | { |
1033 | View* view = View::getView(this); |
1034 | if (!view) |
1035 | return; |
1036 | |
1037 | gfx::Rect vp = view->viewportBounds(); |
1038 | gfx::Point scroll; |
1039 | int x, y, cols; |
1040 | div_t d; |
1041 | const int boxsize = boxSizePx(); |
1042 | |
1043 | scroll = view->viewScroll(); |
1044 | |
1045 | d = div(m_adapter->size(), m_columns); |
1046 | cols = m_columns; |
1047 | |
1048 | y = (boxsize+childSpacing()) * (color / cols); |
1049 | x = (boxsize+childSpacing()) * (color % cols); |
1050 | |
1051 | if (scroll.x > x) |
1052 | scroll.x = x; |
1053 | else if (scroll.x+vp.w-boxsize-2 < x) |
1054 | scroll.x = x-vp.w+boxsize+2; |
1055 | |
1056 | if (scroll.y > y) |
1057 | scroll.y = y; |
1058 | else if (scroll.y+vp.h-boxsize-2 < y) |
1059 | scroll.y = y-vp.h+boxsize+2; |
1060 | |
1061 | view->setViewScroll(scroll); |
1062 | } |
1063 | |
1064 | void PaletteView::onAppPaletteChange() |
1065 | { |
1066 | m_adapter->paletteChange(m_selectedEntries); |
1067 | |
1068 | View* view = View::getView(this); |
1069 | if (view) |
1070 | view->layout(); |
1071 | } |
1072 | |
1073 | gfx::Rect PaletteView::getPaletteEntryBounds(int index) const |
1074 | { |
1075 | const gfx::Rect bounds = clientChildrenBounds(); |
1076 | const int col = index % m_columns; |
1077 | const int row = index / m_columns; |
1078 | const int boxsize = boxSizePx(); |
1079 | const int spacing = childSpacing(); |
1080 | |
1081 | return gfx::Rect( |
1082 | bounds.x + col*boxsize + (col-1)*spacing + (m_withSeparator ? border().left(): 0), |
1083 | bounds.y + row*boxsize + (row-1)*spacing + (m_withSeparator ? border().top(): 0), |
1084 | boxsize, boxsize); |
1085 | } |
1086 | |
1087 | PaletteView::Hit PaletteView::hitTest(const gfx::Point& pos) |
1088 | { |
1089 | auto theme = SkinTheme::get(this); |
1090 | const int outlineWidth = theme->dimensions.paletteOutlineWidth(); |
1091 | const int size = m_adapter->size(); |
1092 | |
1093 | if (m_state == State::WAITING && m_editable) { |
1094 | // First check if the mouse is inside the selection outline. |
1095 | for (int i=0; i<size; ++i) { |
1096 | if (!m_selectedEntries[i]) |
1097 | continue; |
1098 | |
1099 | bool top = (i >= m_columns && i-m_columns >= 0 ? m_selectedEntries[i-m_columns]: false); |
1100 | bool bottom = (i < size-m_columns && i+m_columns < size ? m_selectedEntries[i+m_columns]: false); |
1101 | bool left = ((i%m_columns)>0 && i-1 >= 0 ? m_selectedEntries[i-1]: false); |
1102 | bool right = ((i%m_columns)<m_columns-1 && i+1 < size ? m_selectedEntries[i+1]: false); |
1103 | |
1104 | gfx::Rect box = getPaletteEntryBounds(i); |
1105 | box.enlarge(outlineWidth); |
1106 | |
1107 | if ((!top && gfx::Rect(box.x, box.y, box.w, outlineWidth).contains(pos)) || |
1108 | (!bottom && gfx::Rect(box.x, box.y+box.h-outlineWidth, box.w, outlineWidth).contains(pos)) || |
1109 | (!left && gfx::Rect(box.x, box.y, outlineWidth, box.h).contains(pos)) || |
1110 | (!right && gfx::Rect(box.x+box.w-outlineWidth, box.y, outlineWidth, box.h).contains(pos))) |
1111 | return Hit(Hit::OUTLINE, i); |
1112 | } |
1113 | |
1114 | // Check if we are in the resize handle |
1115 | if (getPaletteEntryBounds(size).contains(pos)) { |
1116 | return Hit(Hit::RESIZE_HANDLE, size); |
1117 | } |
1118 | } |
1119 | |
1120 | // Check if we are inside a color. |
1121 | View* view = View::getView(this); |
1122 | ASSERT(view); |
1123 | gfx::Rect vp = view->viewportBounds(); |
1124 | for (int i=0; ; ++i) { |
1125 | gfx::Rect box = getPaletteEntryBounds(i); |
1126 | if (i >= size && box.y2() > vp.h) |
1127 | break; |
1128 | |
1129 | box.w += childSpacing(); |
1130 | box.h += childSpacing(); |
1131 | if (box.contains(pos)) |
1132 | return Hit(Hit::COLOR, i); |
1133 | } |
1134 | |
1135 | gfx::Rect box = getPaletteEntryBounds(0); |
1136 | |
1137 | int colsLimit = m_columns; |
1138 | if (m_state == State::DRAGGING_OUTLINE) |
1139 | --colsLimit; |
1140 | int i = std::clamp((pos.x-vp.x)/box.w, 0, colsLimit) |
1141 | + std::max(0, pos.y/box.h)*m_columns; |
1142 | return Hit(Hit::POSSIBLE_COLOR, i); |
1143 | } |
1144 | |
1145 | void PaletteView::dropColors(int beforeIndex) |
1146 | { |
1147 | m_adapter->dropColors(this, |
1148 | m_selectedEntries, |
1149 | m_currentEntry, |
1150 | beforeIndex, |
1151 | m_copy); |
1152 | } |
1153 | |
1154 | void PaletteView::getEntryBoundsAndClip(int i, const PalettePicks& entries, |
1155 | const int outlineWidth, |
1156 | gfx::Rect& box, gfx::Rect& clip) const |
1157 | { |
1158 | const int childSpacing = this->childSpacing(); |
1159 | |
1160 | box = clip = getPaletteEntryBounds(i); |
1161 | box.enlarge(outlineWidth); |
1162 | |
1163 | gfx::Border boxBorder(0, 0, 0, 0); |
1164 | gfx::Border clipBorder(0, 0, 0, 0); |
1165 | |
1166 | // Left |
1167 | if (!pickedXY(entries, i, -1, 0)) |
1168 | clipBorder.left(outlineWidth); |
1169 | else { |
1170 | boxBorder.left((childSpacing+1)/2); |
1171 | clipBorder.left((childSpacing+1)/2); |
1172 | } |
1173 | |
1174 | // Top |
1175 | if (!pickedXY(entries, i, 0, -1)) |
1176 | clipBorder.top(outlineWidth); |
1177 | else { |
1178 | boxBorder.top((childSpacing+1)/2); |
1179 | clipBorder.top((childSpacing+1)/2); |
1180 | } |
1181 | |
1182 | // Right |
1183 | if (!pickedXY(entries, i, +1, 0)) |
1184 | clipBorder.right(outlineWidth); |
1185 | else { |
1186 | boxBorder.right(childSpacing/2); |
1187 | clipBorder.right(childSpacing/2); |
1188 | } |
1189 | |
1190 | // Bottom |
1191 | if (!pickedXY(entries, i, 0, +1)) |
1192 | clipBorder.bottom(outlineWidth); |
1193 | else { |
1194 | boxBorder.bottom(childSpacing/2); |
1195 | clipBorder.bottom(childSpacing/2); |
1196 | } |
1197 | |
1198 | box.enlarge(boxBorder); |
1199 | clip.enlarge(clipBorder); |
1200 | } |
1201 | |
1202 | bool PaletteView::pickedXY(const doc::PalettePicks& entries, int i, int dx, int dy) const |
1203 | { |
1204 | const int x = (i % m_columns) + dx; |
1205 | const int y = (i / m_columns) + dy; |
1206 | const int lastcolor = entries.size()-1; |
1207 | |
1208 | if (x < 0 || x >= m_columns || y < 0 || y > lastcolor/m_columns) |
1209 | return false; |
1210 | |
1211 | i = x + y*m_columns; |
1212 | if (i >= 0 && i < entries.size()) |
1213 | return entries[i]; |
1214 | else |
1215 | return false; |
1216 | } |
1217 | |
1218 | void PaletteView::updateCopyFlag(ui::Message* msg) |
1219 | { |
1220 | bool oldCopy = m_copy; |
1221 | m_copy = (msg->ctrlPressed() || msg->altPressed()); |
1222 | if (oldCopy != m_copy) { |
1223 | setCursor(); |
1224 | setStatusBar(); |
1225 | invalidate(); |
1226 | } |
1227 | } |
1228 | |
1229 | void PaletteView::setCursor() |
1230 | { |
1231 | if (m_state == State::DRAGGING_OUTLINE || |
1232 | (m_state == State::WAITING && |
1233 | m_hot.part == Hit::OUTLINE)) { |
1234 | if (m_copy) |
1235 | ui::set_mouse_cursor(kArrowPlusCursor); |
1236 | else |
1237 | ui::set_mouse_cursor(kMoveCursor); |
1238 | } |
1239 | else if (m_state == State::RESIZING_PALETTE || |
1240 | (m_state == State::WAITING && |
1241 | m_hot.part == Hit::RESIZE_HANDLE)) { |
1242 | ui::set_mouse_cursor(kSizeWECursor); |
1243 | } |
1244 | else |
1245 | ui::set_mouse_cursor(kArrowCursor); |
1246 | } |
1247 | |
1248 | void PaletteView::setStatusBar() |
1249 | { |
1250 | StatusBar* statusBar = StatusBar::instance(); |
1251 | |
1252 | if (m_hot.part == Hit::NONE) { |
1253 | statusBar->showDefaultText(); |
1254 | return; |
1255 | } |
1256 | |
1257 | switch (m_state) { |
1258 | |
1259 | case State::WAITING: |
1260 | case State::SELECTING_COLOR: |
1261 | if ((m_hot.part == Hit::COLOR || |
1262 | m_hot.part == Hit::OUTLINE || |
1263 | m_hot.part == Hit::POSSIBLE_COLOR) && |
1264 | (m_hot.color < m_adapter->size())) { |
1265 | const int index = std::max(0, m_hot.color); |
1266 | |
1267 | m_adapter->showEntryInStatusBar(statusBar, index); |
1268 | } |
1269 | else { |
1270 | statusBar->showDefaultText(); |
1271 | } |
1272 | break; |
1273 | |
1274 | case State::DRAGGING_OUTLINE: |
1275 | if (m_hot.part == Hit::COLOR) { |
1276 | const int picks = m_selectedEntries.picks(); |
1277 | const int destIndex = std::max(0, m_hot.color); |
1278 | const int palSize = m_adapter->size(); |
1279 | const int newPalSize = |
1280 | (m_copy ? std::max(palSize + picks, destIndex + picks): |
1281 | std::max(palSize, destIndex + picks)); |
1282 | |
1283 | m_adapter->showDragInfoInStatusBar( |
1284 | statusBar, m_copy, destIndex, newPalSize); |
1285 | } |
1286 | else { |
1287 | statusBar->showDefaultText(); |
1288 | } |
1289 | break; |
1290 | |
1291 | case State::RESIZING_PALETTE: |
1292 | if (m_hot.part == Hit::COLOR || |
1293 | m_hot.part == Hit::POSSIBLE_COLOR || |
1294 | m_hot.part == Hit::RESIZE_HANDLE) { |
1295 | const int newSize = std::max(1, m_hot.color); |
1296 | |
1297 | m_adapter->showResizeInfoInStatusBar(statusBar, newSize); |
1298 | } |
1299 | else { |
1300 | statusBar->showDefaultText(); |
1301 | } |
1302 | break; |
1303 | } |
1304 | } |
1305 | |
1306 | doc::Palette* PaletteView::currentPalette() const |
1307 | { |
1308 | return get_current_palette(); |
1309 | } |
1310 | |
1311 | int PaletteView::findExactIndex(const app::Color& color) const |
1312 | { |
1313 | switch (color.getType()) { |
1314 | |
1315 | case Color::MaskType: { |
1316 | if (current_editor && current_editor->sprite()->pixelFormat() == IMAGE_INDEXED) |
1317 | return current_editor->sprite()->transparentColor(); |
1318 | return currentPalette()->findMaskColor(); |
1319 | } |
1320 | |
1321 | case Color::RgbType: |
1322 | case Color::HsvType: |
1323 | case Color::HslType: |
1324 | case Color::GrayType: |
1325 | return currentPalette()->findExactMatch( |
1326 | color.getRed(), color.getGreen(), color.getBlue(), color.getAlpha(), -1); |
1327 | |
1328 | case Color::IndexType: |
1329 | return color.getIndex(); |
1330 | } |
1331 | |
1332 | ASSERT(false); |
1333 | return -1; |
1334 | } |
1335 | |
1336 | void PaletteView::setNewPalette(doc::Palette* oldPalette, |
1337 | doc::Palette* newPalette, |
1338 | PaletteViewModification mod) |
1339 | { |
1340 | // No differences |
1341 | if (!newPalette->countDiff(oldPalette, nullptr, nullptr)) |
1342 | return; |
1343 | |
1344 | if (m_delegate) { |
1345 | m_delegate->onPaletteViewModification(newPalette, mod); |
1346 | if (m_currentEntry >= 0) |
1347 | m_delegate->onPaletteViewIndexChange(m_currentEntry, ui::kButtonLeft); |
1348 | } |
1349 | |
1350 | set_current_palette(newPalette, false); |
1351 | manager()->invalidate(); |
1352 | } |
1353 | |
1354 | int PaletteView::boxSizePx() const |
1355 | { |
1356 | return m_boxsize*guiscale() |
1357 | + (m_withSeparator ? 0: childSpacing()); |
1358 | } |
1359 | |
1360 | void PaletteView::updateBorderAndChildSpacing() |
1361 | { |
1362 | auto theme = SkinTheme::get(this); |
1363 | const int dim = theme->dimensions.paletteEntriesSeparator(); |
1364 | setBorder(gfx::Border(dim)); |
1365 | setChildSpacing(m_withSeparator ? dim: 0); |
1366 | |
1367 | View* view = View::getView(this); |
1368 | if (view) |
1369 | view->updateView(); |
1370 | |
1371 | invalidate(); |
1372 | } |
1373 | |
1374 | } // namespace app |
1375 | |