Add zoom slider in StatusBar (issue #420)

This commit is contained in:
David Capello 2015-08-14 19:46:48 -03:00
parent fa53692221
commit 4dc6da286e
16 changed files with 263 additions and 56 deletions

View File

@ -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

View File

@ -74,6 +74,9 @@ public:
void onScrollChanged(Editor* editor) override {
updatePreviewEditor(this);
if (isActive())
StatusBar::instance()->updateFromEditor(this);
}
void onAfterFrameChanged(Editor* editor) override {

View File

@ -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);

View File

@ -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);

View File

@ -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));

View File

@ -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<void>(&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<void>(&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

View File

@ -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

70
src/app/ui/zoom_entry.cpp Normal file
View File

@ -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 <cmath>
#include <cstdio>
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

37
src/app/ui/zoom_entry.h Normal file
View File

@ -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<void, const render::Zoom&> ZoomChange;
private:
// SliderDelegate impl
std::string onGetTextFromValue(int value) override;
int onGetValueFromText(const std::string& text) override;
void onValueChange() override;
};
} // namespace app
#endif

View File

@ -54,7 +54,7 @@ void Zoom::out()
}
}
int Zoom::linearScale()
int Zoom::linearScale() const
{
for (int i=0; i<scales_size; ++i) {
// Exact match
@ -82,10 +82,13 @@ Zoom Zoom::fromLinearScale(int i)
// static
int Zoom::findClosestLinearScale(double scale)
{
for (int i=0; i<scales_size-1; ++i) {
double min = double(scales[i ][0]) / double(scales[i ][1]) - 0.5;
double max = double(scales[i+1][0]) / double(scales[i+1][1]) - 0.5;
if (scale >= min && scale <= max)
for (int i=1; i<scales_size-1; ++i) {
double min = double(scales[i-1][0]) / double(scales[i-1][1]);
double mid = double(scales[i ][0]) / double(scales[i ][1]);
double max = double(scales[i+1][0]) / double(scales[i+1][1]);
if (scale >= (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

View File

@ -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);

View File

@ -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<MouseMessage*>(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<bool> 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

View File

@ -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;
};

View File

@ -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();

View File

@ -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<void> Change;
@ -50,6 +60,7 @@ namespace ui {
int m_max;
int m_value;
bool m_readOnly;
SliderDelegate* m_delegate;
};
} // namespace ui

View File

@ -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);
}