From 4dc6da286edab5c786c35f235bcd0b1515c6a270 Mon Sep 17 00:00:00 2001 From: David Capello Date: Fri, 14 Aug 2015 19:46:48 -0300 Subject: [PATCH] Add zoom slider in StatusBar (issue #420) --- src/app/CMakeLists.txt | 1 + src/app/ui/document_view.cpp | 3 ++ src/app/ui/editor/editor.cpp | 4 +- src/app/ui/editor/editor.h | 6 +-- src/app/ui/skin/skin_theme.cpp | 7 +--- src/app/ui/status_bar.cpp | 17 +++++++++ src/app/ui/status_bar.h | 11 +++++- src/app/ui/zoom_entry.cpp | 70 ++++++++++++++++++++++++++++++++++ src/app/ui/zoom_entry.h | 37 ++++++++++++++++++ src/render/zoom.cpp | 18 ++++++--- src/render/zoom.h | 3 +- src/ui/int_entry.cpp | 69 ++++++++++++++++++++++++--------- src/ui/int_entry.h | 8 ++-- src/ui/slider.cpp | 33 ++++++++++++---- src/ui/slider.h | 15 +++++++- src/ui/widget.cpp | 17 +++++---- 16 files changed, 263 insertions(+), 56 deletions(-) create mode 100644 src/app/ui/zoom_entry.cpp create mode 100644 src/app/ui/zoom_entry.h diff --git a/src/app/CMakeLists.txt b/src/app/CMakeLists.txt index c680a29b9..cb8dacab0 100644 --- a/src/app/CMakeLists.txt +++ b/src/app/CMakeLists.txt @@ -375,6 +375,7 @@ add_library(app-lib ui/workspace.cpp ui/workspace_panel.cpp ui/workspace_tabs.cpp + ui/zoom_entry.cpp ui_context.cpp util/autocrop.cpp util/clipboard.cpp diff --git a/src/app/ui/document_view.cpp b/src/app/ui/document_view.cpp index ddfedd4a8..78c55e4cc 100644 --- a/src/app/ui/document_view.cpp +++ b/src/app/ui/document_view.cpp @@ -74,6 +74,9 @@ public: void onScrollChanged(Editor* editor) override { updatePreviewEditor(this); + + if (isActive()) + StatusBar::instance()->updateFromEditor(this); } void onAfterFrameChanged(Editor* editor) override { diff --git a/src/app/ui/editor/editor.cpp b/src/app/ui/editor/editor.cpp index 0498d5674..f728ecc7c 100644 --- a/src/app/ui/editor/editor.cpp +++ b/src/app/ui/editor/editor.cpp @@ -372,7 +372,7 @@ void Editor::setEditorScroll(const gfx::Point& scroll, bool blitValidRegion) } } -void Editor::setEditorZoom(Zoom zoom) +void Editor::setEditorZoom(const render::Zoom& zoom) { setZoomAndCenterInMouse( zoom, ui::get_mouse_position(), @@ -1386,7 +1386,7 @@ bool Editor::isInsideSelection() m_document->mask()->containsPoint(spritePos.x, spritePos.y); } -void Editor::setZoomAndCenterInMouse(Zoom zoom, +void Editor::setZoomAndCenterInMouse(const Zoom& zoom, const gfx::Point& mousePos, ZoomBehavior zoomBehavior) { HideBrushPreview hide(m_brushPreview); diff --git a/src/app/ui/editor/editor.h b/src/app/ui/editor/editor.h index 88e6fac41..1725b7b93 100644 --- a/src/app/ui/editor/editor.h +++ b/src/app/ui/editor/editor.h @@ -121,10 +121,10 @@ namespace app { const render::Zoom& zoom() const { return m_zoom; } const gfx::Point& padding() const { return m_padding; } - void setZoom(render::Zoom zoom) { m_zoom = zoom; } + void setZoom(const render::Zoom& zoom) { m_zoom = zoom; } void setDefaultScroll(); void setEditorScroll(const gfx::Point& scroll, bool blitValidRegion); - void setEditorZoom(render::Zoom zoom); + void setEditorZoom(const render::Zoom& zoom); // Updates the Editor's view. void updateEditor(); @@ -174,7 +174,7 @@ namespace app { // Returns true if the cursor is inside the active mask/selection. bool isInsideSelection(); - void setZoomAndCenterInMouse(render::Zoom zoom, + void setZoomAndCenterInMouse(const render::Zoom& zoom, const gfx::Point& mousePos, ZoomBehavior zoomBehavior); void pasteImage(const Image* image, const Mask* mask); diff --git a/src/app/ui/skin/skin_theme.cpp b/src/app/ui/skin/skin_theme.cpp index 0387f55dc..8ebd4966a 100644 --- a/src/app/ui/skin/skin_theme.cpp +++ b/src/app/ui/skin/skin_theme.cpp @@ -1323,12 +1323,7 @@ void SkinTheme::paintSlider(PaintEvent& ev) // Draw text std::string old_text = widget->getText(); - - { - char buf[128]; - sprintf(buf, "%d", value); - widget->setTextQuiet(buf); - } + widget->setTextQuiet(widget->convertValueToText(value)); { IntersectClip clip(g, Rect(rc.x, rc.y, x-rc.x, rc.h)); diff --git a/src/app/ui/status_bar.cpp b/src/app/ui/status_bar.cpp index 1a4aaeaaf..090937717 100644 --- a/src/app/ui/status_bar.cpp +++ b/src/app/ui/status_bar.cpp @@ -31,6 +31,7 @@ #include "app/ui/status_bar.h" #include "app/ui/timeline.h" #include "app/ui/toolbar.h" +#include "app/ui/zoom_entry.h" #include "app/ui_context.h" #include "app/util/range_utils.h" #include "base/bind.h" @@ -198,6 +199,8 @@ StatusBar::StatusBar() m_currentFrame = new GotoFrameEntry(); m_newFrame = new Button("+"); m_newFrame->Click.connect(Bind(&StatusBar::newFrame, this)); + m_zoomEntry = new ZoomEntry; + m_zoomEntry->ZoomChange.connect(&StatusBar::onChangeZoom, this); setup_mini_look(m_currentFrame); setup_mini_look(m_newFrame); @@ -209,6 +212,7 @@ StatusBar::StatusBar() box1->addChild(m_frameLabel); box1->addChild(box4); + box1->addChild(m_zoomEntry); m_docControls->addChild(box1); } @@ -217,6 +221,7 @@ StatusBar::StatusBar() TooltipManager* tooltipManager = new TooltipManager(); addChild(tooltipManager); tooltipManager->addTooltipFor(m_currentFrame, "Current Frame", BOTTOM); + tooltipManager->addTooltipFor(m_zoomEntry, "Zoom Level", BOTTOM); Preferences::instance().toolBox.activeTool.AfterChange.connect( Bind(&StatusBar::onCurrentToolChange, this)); @@ -250,6 +255,12 @@ void StatusBar::clearText() setStatusText(1, ""); } +void StatusBar::updateFromEditor(Editor* editor) +{ + if (editor) + m_zoomEntry->setZoom(editor->zoom()); +} + bool StatusBar::setStatusText(int msecs, const char *format, ...) { if ((ui::clock() > m_timeout) || (msecs > 0)) { @@ -527,4 +538,10 @@ void StatusBar::newFrame() UIContext::instance()->executeCommand(cmd); } +void StatusBar::onChangeZoom(const render::Zoom& zoom) +{ + if (current_editor) + current_editor->setEditorZoom(zoom); +} + } // namespace app diff --git a/src/app/ui/status_bar.h b/src/app/ui/status_bar.h index 2b7542bf6..120730759 100644 --- a/src/app/ui/status_bar.h +++ b/src/app/ui/status_bar.h @@ -29,10 +29,14 @@ namespace ui { class Window; } +namespace render { + class Zoom; +} + namespace app { class ButtonSet; class Editor; - class StatusBar; + class ZoomEntry; namespace tools { class Tool; @@ -57,6 +61,9 @@ namespace app { void showTool(int msecs, tools::Tool* tool); void showSnapToGridWarning(bool state); + // Used by AppEditor to update the zoom level in the status bar. + void updateFromEditor(Editor* editor); + protected: void onResize(ui::ResizeEvent& ev) override; void onPreferredSize(ui::PreferredSizeEvent& ev) override; @@ -75,6 +82,7 @@ namespace app { void onCurrentToolChange(); void onCelOpacitySliderChange(); void newFrame(); + void onChangeZoom(const render::Zoom& zoom); enum State { SHOW_TEXT, SHOW_COLOR, SHOW_TOOL }; @@ -92,6 +100,7 @@ namespace app { ui::Label* m_frameLabel; ui::Entry* m_currentFrame; // Current frame and go to frame entry ui::Button* m_newFrame; // Button to create a new frame + ZoomEntry* m_zoomEntry; doc::Document* m_doc; // Document used to show the cel slider // Tip window diff --git a/src/app/ui/zoom_entry.cpp b/src/app/ui/zoom_entry.cpp new file mode 100644 index 000000000..c660e0f27 --- /dev/null +++ b/src/app/ui/zoom_entry.cpp @@ -0,0 +1,70 @@ +// Aseprite +// Copyright (C) 2001-2015 David Capello +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License version 2 as +// published by the Free Software Foundation. + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "app/ui/zoom_entry.h" + +#include "app/modules/gui.h" +#include "base/scoped_value.h" +#include "gfx/rect.h" +#include "gfx/region.h" +#include "ui/manager.h" +#include "ui/message.h" +#include "ui/popup_window.h" +#include "ui/slider.h" +#include "ui/system.h" +#include "ui/theme.h" + +#include +#include + +namespace app { + +using namespace gfx; +using namespace ui; + +ZoomEntry::ZoomEntry() + : IntEntry(0, render::Zoom::linearValues()-1, this) +{ + setSuffix("%"); + setup_mini_look(this); + + setZoom(render::Zoom(1, 1)); +} + +void ZoomEntry::setZoom(const render::Zoom& zoom) +{ + setText(onGetTextFromValue(zoom.linearScale())); +} + +void ZoomEntry::onValueChange() +{ + IntEntry::onValueChange(); + + render::Zoom zoom = render::Zoom::fromLinearScale(getValue()); + ZoomChange(zoom); +} + +std::string ZoomEntry::onGetTextFromValue(int value) +{ + render::Zoom zoom = render::Zoom::fromLinearScale(value); + + char buf[256]; + std::sprintf(buf, "%.1f", zoom.scale() * 100.0); + return buf; +} + +int ZoomEntry::onGetValueFromText(const std::string& text) +{ + double value = std::strtod(text.c_str(), nullptr); + return render::Zoom::fromScale(value / 100.0).linearScale(); +} + +} // namespace app diff --git a/src/app/ui/zoom_entry.h b/src/app/ui/zoom_entry.h new file mode 100644 index 000000000..f037b2705 --- /dev/null +++ b/src/app/ui/zoom_entry.h @@ -0,0 +1,37 @@ +// Aseprite +// Copyright (C) 2001-2015 David Capello +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License version 2 as +// published by the Free Software Foundation. + +#ifndef APP_UI_ZOOM_ENTRY_H_INCLUDED +#define APP_UI_ZOOM_ENTRY_H_INCLUDED +#pragma once + +#include "render/zoom.h" +#include "ui/int_entry.h" +#include "ui/slider.h" + +namespace app { + + class ZoomEntry : public ui::IntEntry + , public ui::SliderDelegate { + public: + ZoomEntry(); + + void setZoom(const render::Zoom& zoom); + + Signal1 ZoomChange; + + private: + // SliderDelegate impl + std::string onGetTextFromValue(int value) override; + int onGetValueFromText(const std::string& text) override; + + void onValueChange() override; + }; + +} // namespace app + +#endif diff --git a/src/render/zoom.cpp b/src/render/zoom.cpp index 0e3f8e040..bd1e3ff9c 100644 --- a/src/render/zoom.cpp +++ b/src/render/zoom.cpp @@ -54,7 +54,7 @@ void Zoom::out() } } -int Zoom::linearScale() +int Zoom::linearScale() const { for (int i=0; i= min && scale <= max) + for (int i=1; i= (min+mid)/2.0 && + scale <= (mid+max)/2.0) return i; } if (scale < 1.0) @@ -94,4 +97,9 @@ int Zoom::findClosestLinearScale(double scale) return scales_size-1; } +int Zoom::linearValues() +{ + return scales_size; +} + } // namespace render diff --git a/src/render/zoom.h b/src/render/zoom.h index e62724b33..f7675551c 100644 --- a/src/render/zoom.h +++ b/src/render/zoom.h @@ -53,7 +53,7 @@ namespace render { // Returns an linear zoom scale. This position can be incremented // or decremented to get a new zoom value. - int linearScale(); + int linearScale() const; bool operator==(const Zoom& other) const { return m_num == other.m_num && m_den == other.m_den; @@ -65,6 +65,7 @@ namespace render { static Zoom fromScale(double scale); static Zoom fromLinearScale(int i); + static int linearValues(); private: static int findClosestLinearScale(double scale); diff --git a/src/ui/int_entry.cpp b/src/ui/int_entry.cpp index 5fae080c5..9a7b30a7d 100644 --- a/src/ui/int_entry.cpp +++ b/src/ui/int_entry.cpp @@ -13,9 +13,11 @@ #include "base/scoped_value.h" #include "gfx/rect.h" #include "gfx/region.h" +#include "she/font.h" #include "ui/manager.h" #include "ui/message.h" #include "ui/popup_window.h" +#include "ui/preferred_size_event.h" #include "ui/slider.h" #include "ui/system.h" #include "ui/theme.h" @@ -26,14 +28,17 @@ namespace ui { using namespace gfx; -IntEntry::IntEntry(int min, int max) +IntEntry::IntEntry(int min, int max, SliderDelegate* sliderDelegate) : Entry(int(std::ceil(std::log10((double)max)))+1, "") , m_min(min) , m_max(max) + , m_slider(m_min, m_max, m_min, sliderDelegate) , m_popupWindow(NULL) - , m_slider(NULL) , m_changeFromSlider(false) { + m_slider.setFocusStop(false); // In this way the IntEntry doesn't lost the focus + m_slider.setTransparent(true); + m_slider.Change.connect(&IntEntry::onChangeSlider, this); } IntEntry::~IntEntry() @@ -43,7 +48,7 @@ IntEntry::~IntEntry() int IntEntry::getValue() const { - int value = getTextInt(); + int value = m_slider.convertTextToValue(getText()); return MID(m_min, value, m_max); } @@ -51,10 +56,10 @@ void IntEntry::setValue(int value) { value = MID(m_min, value, m_max); - setTextf("%d", value); + setText(m_slider.convertValueToText(value)); - if (m_slider && !m_changeFromSlider) - m_slider->setValue(value); + if (m_popupWindow && !m_changeFromSlider) + m_slider.setValue(value); onValueChange(); } @@ -81,13 +86,13 @@ bool IntEntry::onProcessMessage(Message* msg) if (hasCapture()) { MouseMessage* mouseMsg = static_cast(msg); Widget* pick = getManager()->pick(mouseMsg->position()); - if (pick == m_slider) { + if (pick == &m_slider) { releaseMouse(); MouseMessage mouseMsg2(kMouseDownMessage, mouseMsg->buttons(), mouseMsg->position()); - m_slider->sendMessage(&mouseMsg2); + m_slider.sendMessage(&mouseMsg2); } } break; @@ -121,6 +126,20 @@ bool IntEntry::onProcessMessage(Message* msg) return Entry::onProcessMessage(msg); } +void IntEntry::onPreferredSize(PreferredSizeEvent& ev) +{ + int min_w = getFont()->textLength(m_slider.convertValueToText(m_min)); + int max_w = getFont()->textLength(m_slider.convertValueToText(m_max)); + + int w = MAX(min_w, max_w) + getFont()->charWidth('%'); + int h = getTextHeight(); + + w += border().width(); + h += border().height(); + + ev.setPreferredSize(w, h); +} + void IntEntry::onEntryChange() { Entry::onEntryChange(); @@ -134,9 +153,17 @@ void IntEntry::onValueChange() void IntEntry::openPopup() { + m_slider.setValue(getValue()); + Rect rc = getBounds(); - rc.y += rc.h; - rc.h += 2*guiscale(); + int sliderH = m_slider.getPreferredSize().h; + + if (rc.y+rc.h+sliderH < ui::display_h()) + rc.y += rc.h; + else + rc.y -= sliderH; + + rc.h = sliderH; rc.w = 128*guiscale(); if (rc.x+rc.w > ui::display_w()) rc.x = rc.x - rc.w + getBounds().w; @@ -152,36 +179,42 @@ void IntEntry::openPopup() rgn.createUnion(rgn, Region(getBounds())); m_popupWindow->setHotRegion(rgn); - m_slider = new Slider(m_min, m_max, getValue()); - m_slider->setFocusStop(false); // In this way the IntEntry doesn't lost the focus - m_slider->setTransparent(true); - m_slider->Change.connect(&IntEntry::onChangeSlider, this); - m_popupWindow->addChild(m_slider); - + m_popupWindow->addChild(&m_slider); m_popupWindow->openWindow(); } void IntEntry::closePopup() { if (m_popupWindow) { + removeSlider(); + m_popupWindow->closeWindow(NULL); delete m_popupWindow; m_popupWindow = NULL; - m_slider = NULL; } } void IntEntry::onChangeSlider() { base::ScopedValue lockFlag(m_changeFromSlider, true, false); - setValue(m_slider->getValue()); + setValue(m_slider.getValue()); selectAllText(); } void IntEntry::onPopupClose(CloseEvent& ev) { + removeSlider(); + deselectText(); releaseFocus(); } +void IntEntry::removeSlider() +{ + if (m_popupWindow && + m_slider.getParent() == m_popupWindow) { + m_popupWindow->removeChild(&m_slider); + } +} + } // namespace ui diff --git a/src/ui/int_entry.h b/src/ui/int_entry.h index 6a5e01a39..2eb8c71e4 100644 --- a/src/ui/int_entry.h +++ b/src/ui/int_entry.h @@ -9,16 +9,16 @@ #pragma once #include "ui/entry.h" +#include "ui/slider.h" namespace ui { class CloseEvent; class PopupWindow; - class Slider; class IntEntry : public Entry { public: - IntEntry(int min, int max); + IntEntry(int min, int max, SliderDelegate* sliderDelegate = nullptr); ~IntEntry(); int getValue() const; @@ -26,6 +26,7 @@ namespace ui { protected: bool onProcessMessage(Message* msg) override; + void onPreferredSize(PreferredSizeEvent& ev) override; void onEntryChange() override; // New events @@ -36,11 +37,12 @@ namespace ui { void closePopup(); void onChangeSlider(); void onPopupClose(CloseEvent& ev); + void removeSlider(); int m_min; int m_max; + Slider m_slider; PopupWindow* m_popupWindow; - Slider* m_slider; bool m_changeFromSlider; }; diff --git a/src/ui/slider.cpp b/src/ui/slider.cpp index ebf0c0fcf..9f581b0cb 100644 --- a/src/ui/slider.cpp +++ b/src/ui/slider.cpp @@ -26,12 +26,13 @@ static int slider_press_x; static int slider_press_value; static bool slider_press_left; -Slider::Slider(int min, int max, int value) +Slider::Slider(int min, int max, int value, SliderDelegate* delegate) : Widget(kSliderWidget) , m_min(min) , m_max(max) , m_value(MID(min, value, max)) , m_readOnly(false) + , m_delegate(delegate) { this->setFocusStop(true); initTheme(); @@ -58,13 +59,33 @@ void Slider::setValue(int value) // It DOES NOT emit CHANGE signal! to avoid recursive calls. } -void Slider::getSliderThemeInfo(int* min, int* max, int* value) +void Slider::getSliderThemeInfo(int* min, int* max, int* value) const { if (min) *min = m_min; if (max) *max = m_max; if (value) *value = m_value; } +std::string Slider::convertValueToText(int value) const +{ + if (m_delegate) + return m_delegate->onGetTextFromValue(value); + else { + char buf[128]; + std::sprintf(buf, "%d", value); + return buf; + } +} + +int Slider::convertTextToValue(const std::string& text) const +{ + if (m_delegate) + return m_delegate->onGetValueFromText(text); + else { + return std::strtol(text.c_str(), NULL, 10); + } +} + bool Slider::onProcessMessage(Message* msg) { switch (msg->type()) { @@ -194,12 +215,8 @@ not_used:; void Slider::onPreferredSize(PreferredSizeEvent& ev) { - char buf[256]; - std::sprintf(buf, "%d", m_min); - int min_w = getFont()->textLength(buf); - - std::sprintf(buf, "%d", m_max); - int max_w = getFont()->textLength(buf); + int min_w = getFont()->textLength(convertValueToText(m_min)); + int max_w = getFont()->textLength(convertValueToText(m_max)); int w = MAX(min_w, max_w); int h = getTextHeight(); diff --git a/src/ui/slider.h b/src/ui/slider.h index ce9f3596d..52796c9f8 100644 --- a/src/ui/slider.h +++ b/src/ui/slider.h @@ -13,9 +13,16 @@ namespace ui { + class SliderDelegate { + public: + virtual ~SliderDelegate() { } + virtual std::string onGetTextFromValue(int value) = 0; + virtual int onGetValueFromText(const std::string& text) = 0; + }; + class Slider : public Widget { public: - Slider(int min, int max, int value); + Slider(int min, int max, int value, SliderDelegate* delegate = nullptr); int getMinValue() const { return m_min; } int getMaxValue() const { return m_max; } @@ -27,7 +34,10 @@ namespace ui { bool isReadOnly() const { return m_readOnly; } void setReadOnly(bool readOnly) { m_readOnly = readOnly; } - void getSliderThemeInfo(int* min, int* max, int* value); + void getSliderThemeInfo(int* min, int* max, int* value) const; + + std::string convertValueToText(int value) const; + int convertTextToValue(const std::string& text) const; // Signals Signal0 Change; @@ -50,6 +60,7 @@ namespace ui { int m_max; int m_value; bool m_readOnly; + SliderDelegate* m_delegate; }; } // namespace ui diff --git a/src/ui/widget.cpp b/src/ui/widget.cpp index 13f6c1005..243684608 100644 --- a/src/ui/widget.cpp +++ b/src/ui/widget.cpp @@ -978,13 +978,16 @@ bool Widget::paintEvent(Graphics* graphics) enableFlags(HIDDEN); - gfx::Region rgn(getParent()->getBounds()); - rgn.createIntersection(rgn, - gfx::Region( - graphics->getClipBounds().offset( - graphics->getInternalDeltaX(), - graphics->getInternalDeltaY()))); - getParent()->paint(graphics, rgn); + if (getParent()) { + gfx::Region rgn(getParent()->getBounds()); + rgn.createIntersection( + rgn, + gfx::Region( + graphics->getClipBounds().offset( + graphics->getInternalDeltaX(), + graphics->getInternalDeltaY()))); + getParent()->paint(graphics, rgn); + } disableFlags(HIDDEN); }