Merge branch 'intuitive-opacity-values' (#1544, #4262)

This commit is contained in:
David Capello 2024-02-08 10:59:42 -03:00
commit 434c262489
22 changed files with 389 additions and 85 deletions

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Aseprite -->
<!-- Copyright (C) 2018-2023 Igara Studio S.A. -->
<!-- Copyright (C) 2018-2024 Igara Studio S.A. -->
<!-- Copyright (C) 2014-2018 David Capello -->
<preferences>
@ -133,6 +133,10 @@
<value id="BILINEAR_MIPMAP" value="2" />
<value id="TRILINEAR_MIPMAP" value="3" />
</enum>
<enum id="AlphaRange">
<value id="EIGHT_BIT" value="0" />
<value id="PERCENTAGE" value="1" />
</enum>
</types>
<global>
@ -431,6 +435,10 @@
<option id="window_profile" type="WindowColorProfile" default="WindowColorProfile::MONITOR" />
<option id="window_profile_name" type="std::string" />
</section>
<section id="range">
<option id="alpha" type="AlphaRange" default="AlphaRange::EIGHT_BIT" />
<option id="opacity" type="AlphaRange" default="AlphaRange::PERCENTAGE" />
</section>
<section id="canvas_size">
<option id="trim_outside" type="bool" default="false" />
</section>

View File

@ -1,5 +1,5 @@
# Aseprite
# Copyright (C) 2018-2023 Igara Studio S.A.
# Copyright (C) 2018-2024 Igara Studio S.A.
# Copyright (C) 2016-2018 David Capello
#
# This work is licensed under the Creative Commons Attribution 4.0
@ -1470,6 +1470,11 @@ use_embedded_cs = Use embedded profile
convert_cs = Convert to working RGB space
assign_cs = Assign working RGB space
ask_cs = Ask
alpha_and_opacity = Alpha && Opacity
alpha_range = Alpha Range:
opacity_range = Opacity Range:
8bit_value = 0-255
percentage = 0%-100%
available_themes = Available Themes
extension_themes = Extension Themes
select_theme = &Select

View File

@ -1,11 +1,11 @@
<!-- Aseprite -->
<!-- Copyright (C) 2020-2023 by Igara Studio S.A. -->
<!-- Copyright (C) 2020-2024 by Igara Studio S.A. -->
<!-- Copyright (C) 2001-2016 by David Capello -->
<gui>
<window id="cel_properties" text="@.title">
<grid id="properties_grid" columns="4">
<label text="@.opacity" />
<slider min="0" max="255" id="opacity" cell_align="horizontal" width="128" cell_hspan="2" />
<opacityslider id="opacity" cell_align="horizontal" width="128" cell_hspan="2" />
<button id="user_data" icon="icon_user_data" tooltip="@.user_data_tooltip" />
<label text="@.zindex" />

View File

@ -14,7 +14,7 @@
<button id="tileset" icon="tiles" tooltip="@.tileset_tooltip" />
<label text="@.opacity" />
<slider id="opacity" min="0" max="255" width="128" cell_align="horizontal" cell_hspan="2" />
<opacityslider id="opacity" width="128" cell_align="horizontal" cell_hspan="2" />
</grid>
</vbox>
</window>

View File

@ -1,5 +1,5 @@
<!-- Aseprite -->
<!-- Copyright (C) 2018-2023 Igara Studio S.A. -->
<!-- Copyright (C) 2018-2024 Igara Studio S.A. -->
<!-- Copyright (C) 2001-2018 David Capello -->
<gui>
<window id="options" text="@.title">
@ -192,7 +192,7 @@
<separator text="@.section_color" horizontal="true" />
<check text="@.color_management" id="color_management" pref="color.manage" />
<grid columns="2">
<grid columns="2">
<label text="@.window_cs" id="window_cs_label" />
<combobox id="window_cs">
<listitem text="@.use_monitor_cs" />
@ -213,20 +213,34 @@
<listitem text="@.convert_cs" />
<listitem text="@.assign_cs" />
<listitem text="@.ask_cs" />
</combobox>
</combobox>
<label text="@.missing_cs" id="missing_cs_label" />
<combobox id="missing_cs">
<listitem text="@.disable_cs" />
<listitem text="@.assign_cs" />
<listitem text="@.ask_cs" />
</combobox>
</grid>
</combobox>
</grid>
<hbox>
<hbox expansive="true" />
<hbox>
<hbox expansive="true" />
<button id="reset_color_management" text="@general.reset" minwidth="60" />
</hbox>
</hbox>
<separator text="@.alpha_and_opacity" horizontal="true" />
<grid columns="2">
<label text="@.alpha_range" id="alpha_range_label" />
<combobox id="alpha">
<listitem text="@.8bit_value" />
<listitem text="@.percentage" />
</combobox>
<label text="@.opacity_range" id="opacity_range_label" />
<combobox id="opacity">
<listitem text="@.8bit_value" />
<listitem text="@.percentage" />
</combobox>
</grid>
</vbox>
<!-- Editor -->

View File

@ -43,10 +43,10 @@
</hbox>
<label text="@.opacity" />
<slider min="0" max="255" id="opacity" cell_align="horizontal" width="128" />
<opacityslider id="opacity" cell_align="horizontal" width="128" />
<label text="@.opacity_step" />
<slider min="0" max="255" id="opacity_step" cell_align="horizontal" width="128" />
<opacityslider id="opacity_step" cell_align="horizontal" width="128" />
<check id="loop_tag" text="@.loop_tags" cell_hspan="2" />
<check id="current_layer" text="@.current_layer" cell_hspan="2" />

View File

@ -1,5 +1,5 @@
# Aseprite
# Copyright (C) 2018-2023 Igara Studio S.A.
# Copyright (C) 2018-2024 Igara Studio S.A.
# Copyright (C) 2001-2018 David Capello
# Generate a ui::Widget for each widget in a XML file
@ -336,6 +336,8 @@ if(ENABLE_UI)
file_selector.cpp
modules/gfx.cpp
modules/gui.cpp
ui/alpha_entry.cpp
ui/alpha_slider.cpp
ui/app_menuitem.cpp
ui/backup_indicator.cpp
ui/browser_view.cpp

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2018-2023 Igara Studio S.A.
// Copyright (C) 2018-2024 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello
//
// This program is distributed under the terms of
@ -776,6 +776,9 @@ public:
}
update_windows_color_profile_from_preferences();
m_pref.range.alpha(static_cast<app::gen::AlphaRange>(alpha()->getSelectedItemIndex()));
m_pref.range.opacity(static_cast<app::gen::AlphaRange>(opacity()->getSelectedItemIndex()));
// Change sprite grid bounds
if (m_context &&
m_context->activeDocument() &&
@ -1073,6 +1076,9 @@ private:
filesWithCs()->setEnabled(state);
missingCsLabel()->setEnabled(state);
missingCs()->setEnabled(state);
alpha()->setSelectedItemIndex(static_cast<int>(m_pref.range.alpha()));
opacity()->setSelectedItemIndex(static_cast<int>(m_pref.range.opacity()));
}
void onResetColorManagement() {

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2019-2023 Igara Studio S.A.
// Copyright (C) 2019-2024 Igara Studio S.A.
//
// This program is distributed under the terms of
// the End-User License Agreement for Aseprite.
@ -334,6 +334,7 @@ FOR_ENUM(app::gen::SymmetryMode)
FOR_ENUM(app::gen::TimelinePosition)
FOR_ENUM(app::gen::ToGrayAlgorithm)
FOR_ENUM(app::gen::WindowColorProfile)
FOR_ENUM(app::gen::AlphaRange)
FOR_ENUM(app::tools::ColorFromTo)
FOR_ENUM(app::tools::DynamicSensor)
FOR_ENUM(app::tools::FreehandAlgorithm)

View File

@ -0,0 +1,67 @@
// Aseprite UI Library
// Copyright (C) 2024 Igara Studio S.A.
//
// This file is released under the terms of the MIT license.
// Read LICENSE.txt for more information.
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "app/pref/preferences.h"
#include "app/ui/alpha_entry.h"
#include "base/scoped_value.h"
#include "gfx/rect.h"
#include "gfx/region.h"
#include "os/font.h"
#include "ui/fit_bounds.h"
#include "ui/manager.h"
#include "ui/message.h"
#include "ui/popup_window.h"
#include "ui/scale.h"
#include "ui/size_hint_event.h"
#include "ui/slider.h"
#include "ui/system.h"
#include "ui/theme.h"
#include <algorithm>
#include <cmath>
namespace app {
using namespace gfx;
AlphaEntry::AlphaEntry(AlphaSlider::Type type)
: IntEntry(0, 255)
{
m_slider = std::make_unique<AlphaSlider>(0, type);
m_slider->setFocusStop(false); // In this way the IntEntry doesn't lost the focus
m_slider->setTransparent(true);
m_slider->Change.connect([this] { this->onChangeSlider(); });
}
int AlphaEntry::getValue() const
{
int value = m_slider->convertTextToValue(text());
if (static_cast<AlphaSlider*>(m_slider.get())->getAlphaRange() == app::gen::AlphaRange::PERCENTAGE)
value = std::round(((double)m_slider->getMaxValue())*((double)value)/((double)100));
return std::clamp(value, m_min, m_max);
}
void AlphaEntry::setValue(int value)
{
value = std::clamp(value, m_min, m_max);
if (m_popupWindow && !m_changeFromSlider)
m_slider->setValue(value);
if (static_cast<AlphaSlider*>(m_slider.get())->getAlphaRange() == app::gen::AlphaRange::PERCENTAGE)
value = std::round(((double)100)*((double)value)/((double)m_slider->getMaxValue()));
setText(m_slider->convertValueToText(value));
onValueChange();
}
} // namespace app

36
src/app/ui/alpha_entry.h Normal file
View File

@ -0,0 +1,36 @@
// Aseprite UI Library
// Copyright (C) 2024 Igara Studio S.A.
//
// This file is released under the terms of the MIT license.
// Read LICENSE.txt for more information.
#ifndef APP_UI_ALPHA_ENTRY_H_INCLUDED
#define APP_UI_ALPHA_ENTRY_H_INCLUDED
#pragma once
#include "ui/int_entry.h"
#include "app/ui/alpha_slider.h"
#include <memory>
using namespace ui;
namespace ui {
class CloseEvent;
class PopupWindow;
}
namespace app {
class AlphaEntry : public IntEntry {
public:
AlphaEntry(AlphaSlider::Type type);
int getValue() const override;
void setValue(int value) override;
};
} // namespace ui
#endif

View File

@ -0,0 +1,69 @@
// Aseprite
// Copyright (C) 2024 Igara Studio S.A.
//
// This program is distributed under the terms of
// the End-User License Agreement for Aseprite.
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "app/ui/alpha_slider.h"
#include "ui/message.h"
#include <algorithm>
using namespace ui;
using namespace app::gen;
namespace app {
AlphaSlider::AlphaSlider(int value, Type type)
: ui::Slider(0, 255, value, this)
, m_type(type)
{
}
void AlphaSlider::getSliderThemeInfo(int* min, int* max, int* value) const
{
switch (getAlphaRange()) {
case AlphaRange::PERCENTAGE:
if (min) *min = 0;
if (max) *max = 100;
if (value) *value = std::round(((double)100)*((double)getValue())/((double)getMaxValue()));
return;
case AlphaRange::EIGHT_BIT:
return Slider::getSliderThemeInfo(min, max, value);
}
}
void AlphaSlider::updateValue(int value)
{
if (getAlphaRange() == AlphaRange::PERCENTAGE)
value = std::round(((double)getMaxValue())*((double)value)/((double)100));
setValue(value);
}
std::string AlphaSlider::onGetTextFromValue(int value)
{
char buf[128];
const char *format = "%d";
if (getAlphaRange() == AlphaRange::PERCENTAGE)
format = "%d%%";
std::snprintf(buf, sizeof(buf), format, value);
return buf;
}
int AlphaSlider::onGetValueFromText(const std::string& text)
{
std::string str = text;
if (getAlphaRange() == AlphaRange::PERCENTAGE)
str.erase(std::remove(str.begin(), str.end(), '%'), str.end());
return std::strtol(str.c_str(), nullptr, 10);
}
} // namespace app

42
src/app/ui/alpha_slider.h Normal file
View File

@ -0,0 +1,42 @@
// Aseprite
// Copyright (C) 2024 Igara Studio S.A.
//
// This program is distributed under the terms of
// the End-User License Agreement for Aseprite.
#ifndef APP_UI_ALPHA_SLIDER_H_INCLUDED
#define APP_UI_ALPHA_SLIDER_H_INCLUDED
#pragma once
#include "app/pref/preferences.h"
#include "ui/slider.h"
namespace app {
class AlphaSlider : public ui::Slider,
ui::SliderDelegate
{
public:
enum Type {ALPHA, OPACITY};
AlphaSlider(int value, Type type);
void getSliderThemeInfo(int* min, int* max, int* value) const override;
void updateValue(int value) override;
app::gen::AlphaRange getAlphaRange() const {
return (m_type == ALPHA ? Preferences::instance().range.alpha()
: Preferences::instance().range.opacity());
}
private:
// ui::SliderDelegate impl
std::string onGetTextFromValue(int value) override;
int onGetValueFromText(const std::string& text) override;
Type m_type;
};
}
#endif

View File

@ -13,6 +13,8 @@
#include "app/color_spaces.h"
#include "app/color_utils.h"
#include "app/modules/gfx.h"
#include "app/ui/alpha_entry.h"
#include "app/ui/alpha_slider.h"
#include "app/ui/color_sliders.h"
#include "app/ui/expr_entry.h"
#include "app/ui/skin/skin_slider_property.h"
@ -141,19 +143,25 @@ namespace {
private:
int minValue() const {
if (m_absSlider->isVisible())
return m_absSlider->getMinValue();
else if (m_relSlider->isVisible())
return m_relSlider->getMinValue();
auto slider = (m_absSlider->isVisible() ? m_absSlider : m_relSlider);
if (slider->isVisible()) {
int min;
slider->getSliderThemeInfo(&min, nullptr, nullptr);
return min;
}
else
return 0;
}
int maxValue() const {
if (m_absSlider->isVisible())
return m_absSlider->getMaxValue();
else if (m_relSlider->isVisible())
return m_relSlider->getMaxValue();
auto slider = (m_absSlider->isVisible() ? m_absSlider : m_relSlider);
if (slider->isVisible()) {
int max;
slider->getSliderThemeInfo(nullptr, &max, nullptr);
return max;
}
else
return 0;
}
@ -373,7 +381,9 @@ void ColorSliders::addSlider(const Channel channel,
ASSERT(!item.label);
item.label = new Label(labelText);
item.box = new HBox();
item.absSlider = new Slider(absMin, absMax, 0);
item.absSlider = (channel != Channel::Alpha
? new Slider(absMin, absMax, 0)
: new AlphaSlider(0, AlphaSlider::Type::ALPHA));
item.relSlider = new Slider(relMin, relMax, 0);
item.entry = new ColorEntry(item.absSlider, item.relSlider);
@ -468,8 +478,7 @@ void ColorSliders::onEntryChange(const Channel i)
Slider* slider = (m_mode == Mode::Absolute ?
m_items[i].absSlider:
m_items[i].relSlider);
value = std::clamp(value, slider->getMinValue(), slider->getMaxValue());
slider->setValue(value);
slider->updateValue(value);
onControlChange(i);
}
@ -494,7 +503,10 @@ void ColorSliders::updateEntryText(const Channel i)
Slider* slider = (m_mode == Mode::Absolute ? m_items[i].absSlider:
m_items[i].relSlider);
m_items[i].entry->setTextf("%d", slider->getValue());
int value;
slider->getSliderThemeInfo(nullptr, nullptr, &value);
m_items[i].entry->setTextf("%d", value);
if (m_items[i].entry->hasFocus())
m_items[i].entry->selectAllText();
}

View File

@ -34,6 +34,7 @@
#include "app/tools/tool.h"
#include "app/tools/tool_box.h"
#include "app/tools/tool_loop_modifiers.h"
#include "app/ui/alpha_entry.h"
#include "app/ui/brush_popup.h"
#include "app/ui/button_set.h"
#include "app/ui/color_button.h"
@ -710,9 +711,9 @@ private:
obs::scoped_connection m_conn;
};
class ContextBar::InkOpacityField : public IntEntry {
class ContextBar::InkOpacityField : public AlphaEntry {
public:
InkOpacityField() : IntEntry(0, 255) {
InkOpacityField() : AlphaEntry(AlphaSlider::Type::OPACITY) {
}
protected:
@ -720,7 +721,7 @@ protected:
if (g_updatingFromCode)
return;
IntEntry::onValueChange();
AlphaEntry::onValueChange();
base::ScopedValue lockFlag(g_updatingFromCode, true);
int newValue = getValue();
@ -1914,6 +1915,8 @@ ContextBar::ContextBar(TooltipManager* tooltipManager,
[this]{ onFgOrBgColorChange(doc::Brush::ImageColor::MainColor); });
m_bgColorConn = pref.colorBar.bgColor.AfterChange.connect(
[this]{ onFgOrBgColorChange(doc::Brush::ImageColor::BackgroundColor); });
m_alphaRangeConn = pref.range.opacity.AfterChange.connect(
[this]{ onOpacityRangeChange(); });
m_keysConn = KeyboardShortcuts::instance()->UserChange.connect(
[this, tooltipManager]{ setupTooltips(tooltipManager); });
m_dropPixelsConn = m_dropPixels->DropPixels.connect(&ContextBar::onDropPixels, this);
@ -2056,6 +2059,11 @@ void ContextBar::onFgOrBgColorChange(doc::Brush::ImageColor imageColor)
}
}
void ContextBar::onOpacityRangeChange()
{
updateForActiveTool();
}
void ContextBar::onDropPixels(ContextBarObserver::DropAction action)
{
notify_observers(&ContextBarObserver::onDropPixels, action);
@ -2143,7 +2151,7 @@ void ContextBar::updateForTool(tools::Tool* tool)
m_contiguous->setSelected(toolPref->contiguous());
m_inkType->setInkTypeIcon(toolPref->ink());
m_inkOpacity->setTextf("%d", toolPref->opacity());
m_inkOpacity->setValue(toolPref->opacity());
hasInkWithOpacity =
((isPaint && tools::inkHasOpacity(toolPref->ink())) ||

View File

@ -123,6 +123,7 @@ namespace app {
void onBrushAngleChange();
void onSymmetryModeChange();
void onFgOrBgColorChange(doc::Brush::ImageColor imageColor);
void onOpacityRangeChange();
void onDropPixels(ContextBarObserver::DropAction action);
void updateSliceFields(const Site& site);
@ -203,6 +204,7 @@ namespace app {
obs::scoped_connection m_symmModeConn;
obs::scoped_connection m_fgColorConn;
obs::scoped_connection m_bgColorConn;
obs::scoped_connection m_alphaRangeConn;
obs::scoped_connection m_keysConn;
obs::scoped_connection m_dropPixelsConn;
obs::scoped_connection m_sizeConn;

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2019-2023 Igara Studio S.A.
// Copyright (C) 2019-2024 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello
//
// This program is distributed under the terms of
@ -17,6 +17,7 @@
#include "app/i18n/strings.h"
#include "app/modules/gui.h"
#include "app/resource_finder.h"
#include "app/ui/alpha_slider.h"
#include "app/ui/button_set.h"
#include "app/ui/color_button.h"
#include "app/ui/drop_down_button.h"
@ -405,6 +406,13 @@ Widget* WidgetLoader::convertXmlElementToWidget(const TiXmlElement* elem, Widget
widget = new Slider(min_value, max_value, min_value);
static_cast<Slider*>(widget)->setReadOnly(readonly);
}
else if (elem_name == "alphaslider" || elem_name == "opacityslider") {
const bool readonly = bool_attr(elem, "readonly", false);
widget = new AlphaSlider(0, (elem_name == "alphaslider"
? AlphaSlider::Type::ALPHA
: AlphaSlider::Type::OPACITY));
static_cast<AlphaSlider*>(widget)->setReadOnly(readonly);
}
else if (elem_name == "textbox") {
const char* text = (elem->GetText() ? elem->GetText(): "");
bool wordwrap = bool_attr(elem, "wordwrap", false);

View File

@ -1,5 +1,5 @@
// Aseprite Code Generator
// Copyright (c) 2021-2023 Igara Studio S.A.
// Copyright (c) 2021-2024 Igara Studio S.A.
// Copyright (c) 2014-2018 David Capello
//
// This file is released under the terms of the MIT license.
@ -157,6 +157,9 @@ static Item convert_to_item(TiXmlElement* elem)
if (name == "slider")
return item.typeIncl("ui::Slider",
"ui/slider.h");
if (name == "alphaslider" || name == "opacityslider")
return item.typeIncl("app::AlphaSlider",
"app/ui/alpha_slider.h");
if (name == "splitter")
return item.typeIncl("ui::Splitter",
"ui/splitter.h");

View File

@ -36,13 +36,13 @@ IntEntry::IntEntry(int min, int max, SliderDelegate* sliderDelegate)
: Entry(int(std::floor(std::log10(double(max))))+1, "")
, m_min(min)
, m_max(max)
, m_slider(m_min, m_max, m_min, sliderDelegate)
, m_popupWindow(nullptr)
, 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);
m_slider = std::make_unique<Slider>(m_min, m_max, m_min, sliderDelegate);
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);
initTheme();
}
@ -53,7 +53,7 @@ IntEntry::~IntEntry()
int IntEntry::getValue() const
{
int value = m_slider.convertTextToValue(text());
int value = m_slider->convertTextToValue(text());
return std::clamp(value, m_min, m_max);
}
@ -61,10 +61,10 @@ void IntEntry::setValue(int value)
{
value = std::clamp(value, m_min, m_max);
setText(m_slider.convertValueToText(value));
setText(m_slider->convertValueToText(value));
if (m_popupWindow && !m_changeFromSlider)
m_slider.setValue(value);
m_slider->setValue(value);
onValueChange();
}
@ -92,7 +92,7 @@ bool IntEntry::onProcessMessage(Message* msg)
MouseMessage* mouseMsg = static_cast<MouseMessage*>(msg);
Widget* pick = manager()->pickFromScreenPos(
display()->nativeWindow()->pointToScreen(mouseMsg->position()));
if (pick == &m_slider) {
if (pick == m_slider.get()) {
releaseMouse();
MouseMessage mouseMsg2(kMouseDownMessage,
@ -140,7 +140,7 @@ bool IntEntry::onProcessMessage(Message* msg)
void IntEntry::onInitTheme(InitThemeEvent& ev)
{
Entry::onInitTheme(ev);
m_slider.initTheme(); // The slider might not be in the popup window
m_slider->initTheme(); // The slider might not be in the popup window
if (m_popupWindow)
m_popupWindow->initTheme();
}
@ -150,8 +150,8 @@ void IntEntry::onSizeHint(SizeHintEvent& ev)
int trailing = font()->textLength(getSuffix());
trailing = std::max(trailing, 2*theme()->getEntryCaretSize(this).w);
int min_w = font()->textLength(m_slider.convertValueToText(m_min));
int max_w = font()->textLength(m_slider.convertValueToText(m_max)) + trailing;
int min_w = font()->textLength(m_slider->convertValueToText(m_min));
int max_w = font()->textLength(m_slider->convertValueToText(m_max)) + trailing;
int w = std::max(min_w, max_w);
int h = textHeight();
@ -175,7 +175,7 @@ void IntEntry::onValueChange()
void IntEntry::openPopup()
{
m_slider.setValue(getValue());
m_slider->setValue(getValue());
// We weren't able to reproduce it, but there are crash reports
// where this openPopup() function is called and the popup is still
@ -186,7 +186,7 @@ void IntEntry::openPopup()
m_popupWindow = std::make_unique<TransparentPopupWindow>(PopupWindow::ClickBehavior::CloseOnClickInOtherWindow);
m_popupWindow->setAutoRemap(false);
m_popupWindow->addChild(&m_slider);
m_popupWindow->addChild(m_slider.get());
m_popupWindow->Close.connect(&IntEntry::onPopupClose, this);
fit_bounds(
@ -229,7 +229,7 @@ void IntEntry::closePopup()
void IntEntry::onChangeSlider()
{
base::ScopedValue lockFlag(m_changeFromSlider, true);
setValue(m_slider.getValue());
setValue(m_slider->getValue());
selectAllText();
}
@ -244,8 +244,8 @@ void IntEntry::onPopupClose(CloseEvent& ev)
void IntEntry::removeSlider()
{
if (m_popupWindow &&
m_slider.parent() == m_popupWindow.get()) {
m_popupWindow->removeChild(&m_slider);
m_slider->parent() == m_popupWindow.get()) {
m_popupWindow->removeChild(m_slider.get());
}
}

View File

@ -22,32 +22,33 @@ namespace ui {
class IntEntry : public Entry {
public:
IntEntry(int min, int max, SliderDelegate* sliderDelegate = nullptr);
~IntEntry();
virtual ~IntEntry();
int getValue() const;
void setValue(int value);
virtual int getValue() const;
virtual void setValue(int value);
protected:
bool onProcessMessage(Message* msg) override;
void onInitTheme(InitThemeEvent& ev) override;
void onSizeHint(SizeHintEvent& ev) override;
void onChange() override;
virtual void onChangeSlider();
// New events
virtual void onValueChange();
int m_min;
int m_max;
std::unique_ptr<PopupWindow> m_popupWindow;
bool m_changeFromSlider;
std::unique_ptr<Slider> m_slider;
private:
void openPopup();
void closePopup();
void onChangeSlider();
void onPopupClose(CloseEvent& ev);
void removeSlider();
int m_min;
int m_max;
Slider m_slider;
std::unique_ptr<PopupWindow> m_popupWindow;
bool m_changeFromSlider;
};
} // namespace ui

View File

@ -1,5 +1,5 @@
// Aseprite UI Library
// Copyright (C) 2019-2022 Igara Studio S.A.
// Copyright (C) 2019-2024 Igara Studio S.A.
// Copyright (C) 2001-2016 David Capello
//
// This file is released under the terms of the MIT license.
@ -24,9 +24,9 @@
namespace ui {
static int slider_press_x;
static int slider_press_value;
static bool slider_press_left;
int Slider::slider_press_x;
int Slider::slider_press_value;
bool Slider::slider_press_left;
Slider::Slider(int min, int max, int value, SliderDelegate* delegate)
: Widget(kSliderWidget)
@ -75,6 +75,11 @@ void Slider::getSliderThemeInfo(int* min, int* max, int* value) const
if (value) *value = m_value;
}
void Slider::updateValue(int value)
{
setValue(value);
}
std::string Slider::convertValueToText(int value) const
{
if (m_delegate)
@ -113,9 +118,11 @@ bool Slider::onProcessMessage(Message* msg)
captureMouse();
{
int value;
getSliderThemeInfo(nullptr, nullptr, &value);
gfx::Point mousePos = static_cast<MouseMessage*>(msg)->position();
slider_press_x = mousePos.x;
slider_press_value = m_value;
slider_press_value = value;
slider_press_left = static_cast<MouseMessage*>(msg)->left();
}
@ -125,27 +132,33 @@ bool Slider::onProcessMessage(Message* msg)
case kMouseMoveMessage:
if (hasCapture()) {
int value, accuracy, range;
int min, max, value, range;
gfx::Rect rc = childrenBounds();
gfx::Point mousePos = static_cast<MouseMessage*>(msg)->positionForDisplay(display());
range = m_max - m_min + 1;
getSliderThemeInfo(&min, &max, &value);
range = max - min + 1;
// With left click
if (slider_press_left) {
value = m_min + range * (mousePos.x - rc.x) / rc.w;
value = min + range * (mousePos.x - rc.x) / rc.w;
}
// With right click
else {
accuracy = std::clamp(rc.w / range, 1, rc.w);
int w = rc.w;
if (rc.w == 0 || range > rc.w) {
w = 1;
range = 1;
}
value = slider_press_value +
(mousePos.x - slider_press_x) / accuracy;
(mousePos.x - slider_press_x) * range / w;
}
value = std::clamp(value, m_min, m_max);
if (m_value != value) {
setValue(value);
value = std::clamp(value, min, max);
if (getValue() != value) {
updateValue(value);
onChange();
}
@ -172,22 +185,24 @@ bool Slider::onProcessMessage(Message* msg)
case kKeyDownMessage:
if (hasFocus() && !isReadOnly()) {
int value = m_value;
int min, max, value, oldValue;
getSliderThemeInfo(&min, &max, &value);
oldValue = value;
switch (static_cast<KeyMessage*>(msg)->scancode()) {
case kKeyLeft: --value; break;
case kKeyRight: ++value; break;
case kKeyPageDown: value -= (m_max-m_min+1)/4; break;
case kKeyPageUp: value += (m_max-m_min+1)/4; break;
case kKeyHome: value = m_min; break;
case kKeyEnd: value = m_max; break;
case kKeyPageDown: value -= (max-min+1)/4; break;
case kKeyPageUp: value += (max-min+1)/4; break;
case kKeyHome: value = min; break;
case kKeyEnd: value = max; break;
default:
goto not_used;
}
value = std::clamp(value, m_min, m_max);
if (m_value != value) {
setValue(value);
value = std::clamp(value, min, max);
if (oldValue != value) {
updateValue(value);
onChange();
}
@ -204,7 +219,7 @@ bool Slider::onProcessMessage(Message* msg)
value = std::clamp(value, m_min, m_max);
if (m_value != value) {
this->setValue(value);
setValue(value);
onChange();
}
return true;

View File

@ -1,5 +1,5 @@
// Aseprite UI Library
// Copyright (C) 2020 Igara Studio S.A.
// Copyright (C) 2020-2024 Igara Studio S.A.
// Copyright (C) 2001-2016 David Capello
//
// This file is released under the terms of the MIT license.
@ -35,7 +35,8 @@ namespace ui {
bool isReadOnly() const { return m_readOnly; }
void setReadOnly(bool readOnly) { m_readOnly = readOnly; }
void getSliderThemeInfo(int* min, int* max, int* value) const;
virtual void getSliderThemeInfo(int* min, int* max, int* value) const;
virtual void updateValue(int value);
std::string convertValueToText(int value) const;
int convertTextToValue(const std::string& text) const;
@ -45,6 +46,10 @@ namespace ui {
obs::signal<void()> SliderReleased;
protected:
static int slider_press_x;
static int slider_press_value;
static bool slider_press_left;
// Events
bool onProcessMessage(Message* msg) override;
void onPaint(PaintEvent& ev) override;