Add new Text tool (fix #28)

This is the first (not yet production-ready) version of the
interactive Text tool. The text input is done with a transparent
ui::Entry, and on each text modification an ExtraCel is rendered with
this same ui::Entry's TextBlob to be displayed in the canvas with the
active zoom level.

The ui::Entry is being painted along the text in the canvas (just for
testing), but this is something to be fixed. Probably it will not be
the case in the future and a fully customized rendering (onPaint())
process will be required.
This commit is contained in:
David Capello 2024-06-11 22:31:13 -03:00
parent ae091726fe
commit 0bf9353a02
35 changed files with 778 additions and 78 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 17 KiB

View File

@ -414,6 +414,7 @@
<part id="tool_filled_ellipse" x="192" y="80" w="16" h="16" /> <part id="tool_filled_ellipse" x="192" y="80" w="16" h="16" />
<part id="tool_contour" x="144" y="96" w="16" h="16" /> <part id="tool_contour" x="144" y="96" w="16" h="16" />
<part id="tool_polygon" x="160" y="96" w="16" h="16" /> <part id="tool_polygon" x="160" y="96" w="16" h="16" />
<part id="tool_text" x="144" y="112" w="16" h="16" />
<part id="tool_blur" x="160" y="112" w="16" h="16" /> <part id="tool_blur" x="160" y="112" w="16" h="16" />
<part id="tool_jumble" x="176" y="112" w="16" h="16" /> <part id="tool_jumble" x="176" y="112" w="16" h="16" />
<part id="tool_configuration" x="144" y="128" w="16" h="16" /> <part id="tool_configuration" x="144" y="128" w="16" h="16" />

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 16 KiB

View File

@ -410,6 +410,7 @@
<part id="tool_filled_ellipse" x="192" y="80" w="16" h="16" /> <part id="tool_filled_ellipse" x="192" y="80" w="16" h="16" />
<part id="tool_contour" x="144" y="96" w="16" h="16" /> <part id="tool_contour" x="144" y="96" w="16" h="16" />
<part id="tool_polygon" x="160" y="96" w="16" h="16" /> <part id="tool_polygon" x="160" y="96" w="16" h="16" />
<part id="tool_text" x="144" y="112" w="16" h="16" />
<part id="tool_blur" x="160" y="112" w="16" h="16" /> <part id="tool_blur" x="160" y="112" w="16" h="16" />
<part id="tool_jumble" x="176" y="112" w="16" h="16" /> <part id="tool_jumble" x="176" y="112" w="16" h="16" />
<part id="tool_configuration" x="144" y="128" w="16" h="16" /> <part id="tool_configuration" x="144" y="128" w="16" h="16" />

View File

@ -66,7 +66,6 @@
<key command="Outline" shortcut="Shift+O" /> <key command="Outline" shortcut="Shift+O" />
<key command="ColorCurve" shortcut="Ctrl+M" /> <key command="ColorCurve" shortcut="Ctrl+M" />
<key command="ColorCurve" shortcut="F10" /> <key command="ColorCurve" shortcut="F10" />
<key command="PasteText" shortcut="T" />
<key command="Options" mac="Cmd+," /> <key command="Options" mac="Cmd+," />
<key command="Options" shortcut="Ctrl+K" mac="Cmd+K" /> <key command="Options" shortcut="Ctrl+K" mac="Cmd+K" />
<key command="KeyboardShortcuts" shortcut="Ctrl+Alt+Shift+K" mac="Cmd+Alt+Shift+K" /> <key command="KeyboardShortcuts" shortcut="Ctrl+Alt+Shift+K" mac="Cmd+Alt+Shift+K" />
@ -611,6 +610,8 @@
<key tool="blur" shortcut="R" /> <key tool="blur" shortcut="R" />
<key tool="jumble" shortcut="R" /> <key tool="jumble" shortcut="R" />
<key tool="text" shortcut="T" />
</tools> </tools>
<!-- Editor Quicktools: these are modifiers to select quickly a <!-- Editor Quicktools: these are modifiers to select quickly a
@ -1506,6 +1507,16 @@
default_brush_size="16" /> default_brush_size="16" />
</group> </group>
<group id="text">
<tool id="text"
text="@.text"
ink="text"
controller="two_points"
pointshape="pixel"
intertwine="as_rectangles"
traceepolicy="last" />
</group>
</tools> </tools>
</gui> </gui>

View File

@ -1811,6 +1811,7 @@ contour = Contour Tool
polygon = Polygon Tool polygon = Polygon Tool
blur = Blur Tool blur = Blur Tool
jumble = Jumble Tool jumble = Jumble Tool
text = Text Tool
shortcut = Shortcut: {0} shortcut = Shortcut: {0}
preview_hide = Hide Preview preview_hide = Hide Preview
preview_show = Show Preview preview_show = Show Preview

View File

@ -403,9 +403,11 @@ if(ENABLE_UI)
ui/editor/play_state.cpp ui/editor/play_state.cpp
ui/editor/scrolling_state.cpp ui/editor/scrolling_state.cpp
ui/editor/select_box_state.cpp ui/editor/select_box_state.cpp
ui/editor/select_text_box_state.cpp
ui/editor/standby_state.cpp ui/editor/standby_state.cpp
ui/editor/state_with_wheel_behavior.cpp ui/editor/state_with_wheel_behavior.cpp
ui/editor/transform_handles.cpp ui/editor/transform_handles.cpp
ui/editor/writing_text_state.cpp
ui/editor/zooming_state.cpp ui/editor/zooming_state.cpp
ui/export_file_window.cpp ui/export_file_window.cpp
ui/expr_entry.cpp ui/expr_entry.cpp

View File

@ -16,7 +16,6 @@
#include "app/pref/preferences.h" #include "app/pref/preferences.h"
#include "app/ui/drop_down_button.h" #include "app/ui/drop_down_button.h"
#include "app/ui/editor/editor.h" #include "app/ui/editor/editor.h"
#include "app/ui/skin/skin_theme.h"
#include "app/ui/timeline/timeline.h" #include "app/ui/timeline/timeline.h"
#include "app/util/render_text.h" #include "app/util/render_text.h"
#include "base/fs.h" #include "base/fs.h"
@ -75,21 +74,7 @@ void PasteTextCommand::onExecute(Context* ctx)
return; return;
Preferences& pref = Preferences::instance(); Preferences& pref = Preferences::instance();
FontInfo fontInfo; FontInfo fontInfo = FontInfo::getFromPreferences();
// Old configuration
if (!pref.textTool.fontFace().empty()) {
fontInfo = FontInfo(FontInfo::Type::File,
pref.textTool.fontFace(),
pref.textTool.fontSize(),
text::FontStyle(),
pref.textTool.antialias());
}
// New configuration
if (!pref.textTool.fontInfo().empty()) {
fontInfo = base::convert_to<FontInfo>(pref.textTool.fontInfo());
}
PasteTextWindow window(fontInfo, pref.colorBar.fgColor()); PasteTextWindow window(fontInfo, pref.colorBar.fgColor());
window.userText()->setText(last_text_used); window.userText()->setText(last_text_used);
@ -101,20 +86,14 @@ void PasteTextCommand::onExecute(Context* ctx)
last_text_used = window.userText()->text(); last_text_used = window.userText()->text();
fontInfo = window.fontInfo(); fontInfo = window.fontInfo();
pref.textTool.fontInfo(base::convert_to<std::string>(fontInfo)); fontInfo.updatePreferences();
if (!pref.textTool.fontFace().empty()) {
pref.textTool.fontFace.clearValue();
pref.textTool.fontSize.clearValue();
pref.textTool.antialias.clearValue();
}
try { try {
auto* theme = skin::SkinTheme::instance();
std::string text = window.userText()->text(); std::string text = window.userText()->text();
app::Color color = window.fontColor()->getColor(); app::Color color = window.fontColor()->getColor();
doc::ImageRef image = render_text( doc::ImageRef image = render_text(
theme->fontMgr(), fontInfo, text, fontInfo, text,
gfx::rgba(color.getRed(), gfx::rgba(color.getRed(),
color.getGreen(), color.getGreen(),
color.getBlue(), color.getBlue(),

View File

@ -9,6 +9,7 @@
#endif #endif
#include "app/font_info.h" #include "app/font_info.h"
#include "app/pref/preferences.h"
#include "base/fs.h" #include "base/fs.h"
#include "base/split_string.h" #include "base/split_string.h"
#include "fmt/format.h" #include "fmt/format.h"
@ -76,6 +77,39 @@ text::TypefaceRef FontInfo::findTypeface(const text::FontMgrRef& fontMgr) const
return nullptr; return nullptr;
} }
// static
FontInfo FontInfo::getFromPreferences()
{
Preferences& pref = Preferences::instance();
FontInfo fontInfo;
// Old configuration
if (!pref.textTool.fontFace().empty()) {
fontInfo = FontInfo(FontInfo::Type::File,
pref.textTool.fontFace(),
pref.textTool.fontSize(),
text::FontStyle(),
pref.textTool.antialias());
}
// New configuration
if (!pref.textTool.fontInfo().empty()) {
fontInfo = base::convert_to<FontInfo>(pref.textTool.fontInfo());
}
return fontInfo;
}
void FontInfo::updatePreferences()
{
Preferences& pref = Preferences::instance();
pref.textTool.fontInfo(base::convert_to<std::string>(*this));
if (!pref.textTool.fontFace().empty()) {
pref.textTool.fontFace.clearValue();
pref.textTool.fontSize.clearValue();
pref.textTool.antialias.clearValue();
}
}
} // namespace app } // namespace app
namespace base { namespace base {

View File

@ -61,6 +61,9 @@ namespace app {
text::TypefaceRef findTypeface(const text::FontMgrRef& fontMgr) const; text::TypefaceRef findTypeface(const text::FontMgrRef& fontMgr) const;
static FontInfo getFromPreferences();
void updatePreferences();
bool operator==(const FontInfo& other) const { bool operator==(const FontInfo& other) const {
return (m_type == other.m_type && return (m_type == other.m_type &&
m_name == other.m_name && m_name == other.m_name &&

View File

@ -1,5 +1,5 @@
// Aseprite // Aseprite
// Copyright (C) 2018-2020 Igara Studio S.A. // Copyright (C) 2018-2022 Igara Studio S.A.
// Copyright (C) 2001-2017 David Capello // Copyright (C) 2001-2017 David Capello
// //
// This program is distributed under the terms of // This program is distributed under the terms of
@ -68,6 +68,9 @@ namespace app {
// Returns true if this ink is used to mark slices // Returns true if this ink is used to mark slices
virtual bool isSlice() const { return false; } virtual bool isSlice() const { return false; }
// Returns true if this ink acts like the text tool
virtual bool isText() const { return false; }
// Returns true if this tool uses the dithering options // Returns true if this tool uses the dithering options
virtual bool withDitheringOptions() const { return false; } virtual bool withDitheringOptions() const { return false; }

View File

@ -529,5 +529,17 @@ public:
}; };
class TextInk : public BaseInk {
public:
Ink* clone() override { return new TextInk(*this); }
bool isText() const override { return true; }
void prepareInk(ToolLoop* loop) override {
setProc(get_ink_proc<XorInkProcessing>(loop));
}
};
} // namespace tools } // namespace tools
} // namespace app } // namespace app

View File

@ -75,6 +75,7 @@ const char* WellKnownInks::Slice = "slice";
const char* WellKnownInks::MoveSlice = "move_slice"; const char* WellKnownInks::MoveSlice = "move_slice";
const char* WellKnownInks::Blur = "blur"; const char* WellKnownInks::Blur = "blur";
const char* WellKnownInks::Jumble = "jumble"; const char* WellKnownInks::Jumble = "jumble";
const char* WellKnownInks::Text = "text";
const char* WellKnownControllers::Freehand = "freehand"; const char* WellKnownControllers::Freehand = "freehand";
const char* WellKnownControllers::PointByPoint = "point_by_point"; const char* WellKnownControllers::PointByPoint = "point_by_point";
@ -135,6 +136,7 @@ ToolBox::ToolBox()
m_inks[WellKnownInks::Slice] = new SliceInk(); m_inks[WellKnownInks::Slice] = new SliceInk();
m_inks[WellKnownInks::Blur] = new BlurInk(); m_inks[WellKnownInks::Blur] = new BlurInk();
m_inks[WellKnownInks::Jumble] = new JumbleInk(); m_inks[WellKnownInks::Jumble] = new JumbleInk();
m_inks[WellKnownInks::Text] = new TextInk();
m_controllers[WellKnownControllers::Freehand] = new FreehandController(); m_controllers[WellKnownControllers::Freehand] = new FreehandController();
m_controllers[WellKnownControllers::PointByPoint] = new PointByPointController(); m_controllers[WellKnownControllers::PointByPoint] = new PointByPointController();

View File

@ -56,6 +56,7 @@ namespace app {
extern const char* MoveSlice; extern const char* MoveSlice;
extern const char* Blur; extern const char* Blur;
extern const char* Jumble; extern const char* Jumble;
extern const char* Text;
}; };
namespace WellKnownControllers { namespace WellKnownControllers {

View File

@ -43,6 +43,7 @@
#include "app/ui/dynamics_popup.h" #include "app/ui/dynamics_popup.h"
#include "app/ui/editor/editor.h" #include "app/ui/editor/editor.h"
#include "app/ui/expr_entry.h" #include "app/ui/expr_entry.h"
#include "app/ui/font_entry.h"
#include "app/ui/icon_button.h" #include "app/ui/icon_button.h"
#include "app/ui/keyboard_shortcuts.h" #include "app/ui/keyboard_shortcuts.h"
#include "app/ui/layer_frame_comboboxes.h" #include "app/ui/layer_frame_comboboxes.h"
@ -1837,6 +1838,22 @@ private:
std::string m_filter; std::string m_filter;
}; };
class ContextBar::FontSelector : public FontEntry {
public:
FontSelector(ContextBar* contextBar) {
// Load the font from the preferences
setInfo(FontInfo::getFromPreferences());
FontChange.connect([contextBar](){
contextBar->FontChange();
});
}
~FontSelector() {
info().updatePreferences();
}
};
ContextBar::ContextBar(TooltipManager* tooltipManager, ContextBar::ContextBar(TooltipManager* tooltipManager,
ColorBar* colorBar) ColorBar* colorBar)
{ {
@ -1891,6 +1908,7 @@ ContextBar::ContextBar(TooltipManager* tooltipManager,
m_symmetry->setVisible(pref.symmetryMode.enabled()); m_symmetry->setVisible(pref.symmetryMode.enabled());
addChild(m_sliceFields = new SliceFields); addChild(m_sliceFields = new SliceFields);
addChild(m_fontSelector = new FontSelector(this));
setupTooltips(tooltipManager); setupTooltips(tooltipManager);
@ -2183,6 +2201,11 @@ void ContextBar::updateForTool(tools::Tool* tool)
(tool->getInk(0)->isSlice() || (tool->getInk(0)->isSlice() ||
tool->getInk(1)->isSlice()); tool->getInk(1)->isSlice());
// True if the current tool is text tool.
const bool isText = tool &&
(tool->getInk(0)->isText() ||
tool->getInk(1)->isText());
// True if the current tool is floodfill // True if the current tool is floodfill
const bool isFloodfill = tool && const bool isFloodfill = tool &&
(tool->getPointShape(0)->isFloodFill() || (tool->getPointShape(0)->isFloodFill() ||
@ -2267,6 +2290,8 @@ void ContextBar::updateForTool(tools::Tool* tool)
if (isSlice) if (isSlice)
updateSliceFields(UIContext::instance()->activeSite()); updateSliceFields(UIContext::instance()->activeSite());
m_fontSelector->setVisible(isText);
// Update ink shades with the current selected palette entries // Update ink shades with the current selected palette entries
if (updateShade) if (updateShade)
m_inkShades->updateShadeFromColorBarPicks(); m_inkShades->updateShadeFromColorBarPicks();
@ -2554,6 +2579,11 @@ void ContextBar::setInkType(tools::InkType type)
m_inkType->setInkType(type); m_inkType->setInkType(type);
} }
FontInfo ContextBar::fontInfo() const
{
return m_fontSelector->info();
}
render::DitheringMatrix ContextBar::ditheringMatrix() render::DitheringMatrix ContextBar::ditheringMatrix()
{ {
return m_ditheringSelector->ditheringMatrix(); return m_ditheringSelector->ditheringMatrix();

View File

@ -1,5 +1,5 @@
// Aseprite // Aseprite
// Copyright (C) 2018-2022 Igara Studio S.A. // Copyright (C) 2018-2024 Igara Studio S.A.
// Copyright (C) 2001-2017 David Capello // Copyright (C) 2001-2017 David Capello
// //
// This program is distributed under the terms of // This program is distributed under the terms of
@ -52,6 +52,7 @@ namespace app {
class BrushSlot; class BrushSlot;
class ColorBar; class ColorBar;
class DitheringSelector; class DitheringSelector;
class FontInfo;
class GradientTypeSelector; class GradientTypeSelector;
class SamplingSelector; class SamplingSelector;
class Transformation; class Transformation;
@ -89,6 +90,9 @@ namespace app {
void setInkType(tools::InkType type); void setInkType(tools::InkType type);
// For text tool
FontInfo fontInfo() const;
// For gradients // For gradients
render::DitheringMatrix ditheringMatrix(); render::DitheringMatrix ditheringMatrix();
render::DitheringAlgorithmBase* ditheringAlgorithm(); render::DitheringAlgorithmBase* ditheringAlgorithm();
@ -99,6 +103,7 @@ namespace app {
// Signals // Signals
obs::signal<void()> BrushChange; obs::signal<void()> BrushChange;
obs::signal<void()> FontChange;
protected: protected:
void onInitTheme(ui::InitThemeEvent& ev) override; void onInitTheme(ui::InitThemeEvent& ev) override;
@ -163,6 +168,7 @@ namespace app {
class AutoSelectLayerField; class AutoSelectLayerField;
class SymmetryField; class SymmetryField;
class SliceFields; class SliceFields;
class FontSelector;
ZoomButtons* m_zoomButtons; ZoomButtons* m_zoomButtons;
SamplingSelector* m_samplingSelector; SamplingSelector* m_samplingSelector;
@ -201,6 +207,7 @@ namespace app {
ui::Label* m_selectBoxHelp; ui::Label* m_selectBoxHelp;
SymmetryField* m_symmetry; SymmetryField* m_symmetry;
SliceFields* m_sliceFields; SliceFields* m_sliceFields;
FontSelector* m_fontSelector = nullptr;
obs::scoped_connection m_symmModeConn; obs::scoped_connection m_symmModeConn;
obs::scoped_connection m_fgColorConn; obs::scoped_connection m_fgColorConn;
obs::scoped_connection m_bgColorConn; obs::scoped_connection m_bgColorConn;

View File

@ -2237,6 +2237,9 @@ void Editor::onResize(ui::ResizeEvent& ev)
{ {
Widget::onResize(ev); Widget::onResize(ev);
m_padding = calcExtraPadding(m_proj); m_padding = calcExtraPadding(m_proj);
if (m_state)
m_state->onEditorResize(this);
} }
void Editor::onPaint(ui::PaintEvent& ev) void Editor::onPaint(ui::PaintEvent& ev)

View File

@ -83,6 +83,11 @@ namespace app {
// Called when the editor gets the focus. // Called when the editor gets the focus.
virtual void onEditorGotFocus(Editor* editor) { } virtual void onEditorGotFocus(Editor* editor) { }
// Called when the editor is resized. E.g. If a EditorState adds a
// temporary widget inside the editor, this method can layout that
// widget.
virtual void onEditorResize(Editor* editor) { }
// Called when the user presses a mouse button over the editor. // Called when the user presses a mouse button over the editor.
virtual bool onMouseDown(Editor* editor, ui::MouseMessage* msg) { return false; } virtual bool onMouseDown(Editor* editor, ui::MouseMessage* msg) { return false; }

View File

@ -0,0 +1,71 @@
// Aseprite
// Copyright (c) 2022-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/editor/select_text_box_state.h"
#include "app/ui/editor/editor.h"
#include "app/ui/editor/writing_text_state.h"
#include "app/ui/status_bar.h"
#include "fmt/format.h"
namespace app {
SelectTextBoxState::SelectTextBoxState(Editor* editor,
ui::MouseMessage* msg)
: SelectBoxState(this, editor->sprite()->bounds(),
Flags(int(Flags::QuickBox) |
int(Flags::DarkOutside)))
, m_editor(editor)
{
onMouseDown(editor, msg);
}
bool SelectTextBoxState::onUpdateStatusBar(Editor* editor)
{
gfx::PointF spritePos =
editor->screenToEditorF(editor->mousePosInDisplay())
- gfx::PointF(editor->mainTilePosition());
const gfx::Rect bounds = getBoxBounds();
const std::string buf = fmt::format(
":pos: {} {}"
" :start: {} {} :size: {} {}",
int(spritePos.x), int(spritePos.y),
int(bounds.x), int(bounds.y),
int(bounds.w), int(bounds.h));
StatusBar::instance()->setStatusText(0, buf);
return true;
}
void SelectTextBoxState::onChangeRectangle(const gfx::Rect&)
{
onUpdateStatusBar(m_editor);
}
void SelectTextBoxState::onQuickboxEnd(Editor* editor,
const gfx::Rect& rect,
ui::MouseButton)
{
editor->backToPreviousState();
// A 1x1 rectangle will cancel the operation
if (rect.w > 1 && rect.h > 1) {
EditorStatePtr newState = std::make_shared<WritingTextState>(editor, rect);
editor->setState(newState);
}
}
void SelectTextBoxState::onQuickboxCancel(Editor* editor)
{
editor->backToPreviousState();
}
} // namespace app

View File

@ -0,0 +1,35 @@
// Aseprite
// Copyright (c) 2022-2024 Igara Studio S.A.
//
// This program is distributed under the terms of
// the End-User License Agreement for Aseprite.
#ifndef APP_UI_EDITOR_SELECT_TEXT_BOX_STATE_H_INCLUDED
#define APP_UI_EDITOR_SELECT_TEXT_BOX_STATE_H_INCLUDED
#pragma once
#include "app/ui/editor/select_box_state.h"
namespace app {
class SelectTextBoxState : public SelectBoxState
, public SelectBoxDelegate {
public:
SelectTextBoxState(Editor* editor, ui::MouseMessage* msg);
bool isTemporalState() const override { return true; }
bool onUpdateStatusBar(Editor* editor) override;
// SelectBoxDelegate impl
void onChangeRectangle(const gfx::Rect& rect) override;
void onQuickboxEnd(Editor* editor, const gfx::Rect& rect, ui::MouseButton button) override;
void onQuickboxCancel(Editor* editor) override;
private:
Editor* m_editor;
};
} // namespace app
#endif // APP_UI_EDITOR_DRAWING_STATE_H_INCLUDED

View File

@ -41,6 +41,7 @@
#include "app/ui/editor/pivot_helpers.h" #include "app/ui/editor/pivot_helpers.h"
#include "app/ui/editor/pixels_movement.h" #include "app/ui/editor/pixels_movement.h"
#include "app/ui/editor/scrolling_state.h" #include "app/ui/editor/scrolling_state.h"
#include "app/ui/editor/select_text_box_state.h"
#include "app/ui/editor/tool_loop_impl.h" #include "app/ui/editor/tool_loop_impl.h"
#include "app/ui/editor/transform_handles.h" #include "app/ui/editor/transform_handles.h"
#include "app/ui/editor/vec2.h" #include "app/ui/editor/vec2.h"
@ -229,6 +230,7 @@ bool StandbyState::onMouseDown(Editor* editor, MouseMessage* msg)
return true; return true;
} }
// Handle Slice tool
if (clickedInk->isSlice()) { if (clickedInk->isSlice()) {
EditorHit hit = editor->calcHit(msg->position()); EditorHit hit = editor->calcHit(msg->position());
switch (hit.type()) { switch (hit.type()) {
@ -267,6 +269,13 @@ bool StandbyState::onMouseDown(Editor* editor, MouseMessage* msg)
} }
} }
// Handle Text tool
if (clickedInk->isText()) {
EditorStatePtr newState(new SelectTextBoxState(editor, msg));
editor->setState(newState);
return true;
}
// Only if the selected tool or quick tool is selection, we give the // Only if the selected tool or quick tool is selection, we give the
// possibility to transform/move the selection. In other case, // possibility to transform/move the selection. In other case,
// e.g. when selection is used with right-click mode, the // e.g. when selection is used with right-click mode, the
@ -485,6 +494,10 @@ bool StandbyState::onSetCursor(Editor* editor, const gfx::Point& mouseScreenPos)
return true; return true;
} }
} }
else if (ink->isText()) {
editor->showMouseCursor(kCrosshairCursor);
return true;
}
} }
return StateWithWheelBehavior::onSetCursor(editor, mouseScreenPos); return StateWithWheelBehavior::onSetCursor(editor, mouseScreenPos);

View File

@ -0,0 +1,349 @@
// Aseprite
// Copyright (C) 2022-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/editor/writing_text_state.h"
#include "app/app.h"
#include "app/color_utils.h"
#include "app/commands/command.h"
#include "app/extra_cel.h"
#include "app/font_info.h"
#include "app/pref/preferences.h"
#include "app/site.h"
#include "app/tx.h"
#include "app/ui/context_bar.h"
#include "app/ui/editor/editor.h"
#include "app/ui/skin/skin_theme.h"
#include "app/ui/status_bar.h"
#include "app/ui_context.h"
#include "app/util/expand_cel_canvas.h"
#include "app/util/render_text.h"
#include "doc/blend_image.h"
#include "doc/blend_internals.h"
#include "doc/layer.h"
#include "render/dithering.h"
#include "render/quantization.h"
#include "render/render.h"
#include "ui/entry.h"
#include "ui/message.h"
#include "ui/paint_event.h"
namespace app {
using namespace ui;
class WritingTextState::TextEditor : public Entry {
public:
TextEditor(Editor* editor,
const Site& site,
const gfx::Rect& bounds)
: Entry(4096, "")
, m_editor(editor)
, m_doc(site.document())
, m_extraCel(new ExtraCel) {
// We have to draw the editor as background of this ui::Entry.
setTransparent(true);
// TODO move this opacity() to Site class
int t, opacity = (site.layer()->isImage() ?
static_cast<LayerImage*>(site.layer())->opacity(): 255);
Cel* cel = site.cel();
if (cel) opacity = MUL_UN8(opacity, cel->opacity(), t);
m_extraCel->create(
site.tilemapMode(),
site.sprite(),
bounds,
bounds.size(),
site.frame(),
255);
m_extraCel->setType(render::ExtraType::PATCH);
m_extraCel->setBlendMode(site.layer()->isImage() ?
static_cast<LayerImage*>(site.layer())->blendMode():
doc::BlendMode::NORMAL);
renderExtraCelBase();
FontInfo fontInfo = App::instance()->contextBar()->fontInfo();
if (auto font = get_font_from_info(fontInfo))
setFont(font);
}
~TextEditor() {
m_doc->setExtraCel(ExtraCelRef(nullptr));
m_doc->generateMaskBoundaries();
}
ExtraCelRef extraCel() const { return m_extraCel; }
private:
bool onProcessMessage(Message* msg) override {
switch (msg->type()) {
case kMouseDownMessage:
case kMouseMoveMessage: {
auto* mouseMsg = static_cast<MouseMessage*>(msg);
// Ignore middle mouse button so we can scroll with it.
if (mouseMsg->middle()) {
auto* parent = this->parent();
MouseMessage mouseMsg2(kMouseDownMessage,
*mouseMsg,
mouseMsg->position());
mouseMsg2.setRecipient(parent);
parent->sendMessage(&mouseMsg2);
return true;
}
break;
}
}
return Entry::onProcessMessage(msg);
}
void onInitTheme(InitThemeEvent& ev) override {
Entry::onInitTheme(ev);
setBgColor(gfx::ColorNone);
}
void onPaint(PaintEvent& ev) override {
Entry::onPaint(ev);
if (!hasText())
return;
try {
text::TextBlobRef blob = textBlob();
if (!blob) {
m_doc->setExtraCel(nullptr);
m_editor->invalidate();
return;
}
const auto textColor =
color_utils::color_for_image(
Preferences::instance().colorBar.fgColor(),
IMAGE_RGB);
doc::ImageRef image = render_text_blob(blob, textColor);
if (image) {
renderExtraCelBase();
doc::blend_image(
m_extraCel->image(), image.get(),
gfx::Clip(image->bounds().size()),
m_doc->sprite()->palette(m_editor->frame()),
255, doc::BlendMode::NORMAL);
m_doc->setExtraCel(m_extraCel);
m_editor->invalidate();
}
}
catch (const std::exception& e) {
StatusBar::instance()->showTip(500, std::string(e.what()));
}
}
void renderExtraCelBase() {
auto extraImg = m_extraCel->image();
extraImg->clear(extraImg->maskColor());
render::Render().renderLayer(
extraImg,
m_editor->layer(),
m_editor->frame(),
gfx::Clip(0, 0, m_extraCel->cel()->bounds()),
doc::BlendMode::SRC);
}
Editor* m_editor;
Doc* m_doc;
ExtraCelRef m_extraCel;
};
WritingTextState::WritingTextState(Editor* editor,
const gfx::Rect& bounds)
: m_editor(editor)
, m_bounds(bounds)
, m_entry(new TextEditor(editor, editor->getSite(), bounds))
{
m_beforeCmdConn =
UIContext::instance()->BeforeCommandExecution.connect(
&WritingTextState::onBeforeCommandExecution, this);
m_fontChangeConn =
App::instance()->contextBar()->FontChange.connect(
&WritingTextState::onFontChange, this);
m_entry->setBounds(calcEntryBounds());
}
WritingTextState::~WritingTextState()
{
}
bool WritingTextState::onMouseDown(Editor* editor, MouseMessage* msg)
{
if (msg->left()) {
drop();
}
else if (msg->right()) {
cancel();
return true;
}
return StandbyState::onMouseDown(editor, msg);
}
bool WritingTextState::onMouseUp(Editor* editor, MouseMessage* msg)
{
const bool result = StandbyState::onMouseUp(editor, msg);
if (msg->middle()) {
if (m_entry)
m_entry->requestFocus();
}
return result;
}
bool WritingTextState::onSetCursor(Editor* editor, const gfx::Point&)
{
editor->showMouseCursor(kArrowCursor);
return true;
}
bool WritingTextState::onKeyDown(Editor*, KeyMessage*)
{
return false;
}
bool WritingTextState::onKeyUp(Editor*, KeyMessage* msg)
{
// Cancel loop pressing Esc key
if (msg->scancode() == ui::kKeyEsc) {
cancel();
}
// Drop text pressing Enter key
else if (msg->scancode() == ui::kKeyEnter) {
drop();
}
return true;
}
void WritingTextState::onEditorResize(Editor*)
{
m_entry->setBounds(calcEntryBounds());
}
gfx::Rect WritingTextState::calcEntryBounds()
{
const View* view = View::getView(m_editor);
const gfx::Rect vp = view->viewportBounds();
const gfx::Point scroll = view->viewScroll();
const auto& padding = m_editor->padding();
const auto& proj = m_editor->projection();
gfx::Point pt1(m_bounds.origin());
gfx::Point pt2(m_bounds.point2());
pt1.x = vp.x - scroll.x + padding.x + proj.applyX(pt1.x);
pt1.y = vp.y - scroll.y + padding.y + proj.applyY(pt1.y);
pt2.x = vp.x - scroll.x + padding.x + proj.applyX(pt2.x);
pt2.y = vp.y - scroll.y + padding.y + proj.applyY(pt2.y);
return gfx::Rect(pt1, pt2);
}
void WritingTextState::onEnterState(Editor* editor)
{
StandbyState::onEnterState(editor);
editor->invalidate();
editor->addChild(m_entry.get());
m_entry->requestFocus();
}
EditorState::LeaveAction WritingTextState::onLeaveState(Editor* editor, EditorState* newState)
{
if (!newState || !newState->isTemporalState()) {
if (!m_discarded) {
// Paints the text in the active layer/sprite creating an
// undoable transaction.
Site site = m_editor->getSite();
ExtraCelRef extraCel = m_entry->extraCel();
Tx tx(site.document(), "Text Tool");
ExpandCelCanvas expand(
site, site.layer(),
TiledMode::NONE, tx,
ExpandCelCanvas::None);
expand.validateDestCanvas(
gfx::Region(extraCel->cel()->bounds()));
doc::blend_image(
expand.getDestCanvas(),
extraCel->image(),
gfx::Clip(extraCel->cel()->position(),
extraCel->image()->bounds()),
site.palette(),
255, doc::BlendMode::NORMAL);
expand.commit();
tx.commit();
}
m_editor->releaseMouse();
m_editor->document()->notifyGeneralUpdate();
return DiscardState;
}
editor->releaseMouse();
return KeepState;
}
void WritingTextState::onBeforePopState(Editor* editor)
{
editor->removeChild(m_entry.get());
m_beforeCmdConn.disconnect();
m_fontChangeConn.disconnect();
StandbyState::onBeforePopState(editor);
}
void WritingTextState::onBeforeCommandExecution(CommandExecutionEvent& ev)
{
if (// Undo/Redo/Cancel will cancel this state
ev.command()->id() == CommandId::Undo() ||
ev.command()->id() == CommandId::Redo() ||
ev.command()->id() == CommandId::Cancel()) {
cancel();
}
else {
drop();
}
}
void WritingTextState::onFontChange()
{
const FontInfo fontInfo = App::instance()->contextBar()->fontInfo();
if (auto font = get_font_from_info(fontInfo)) {
m_entry->setFont(font);
m_entry->invalidate();
m_editor->invalidate();
}
}
void WritingTextState::cancel()
{
m_discarded = true;
m_editor->backToPreviousState();
m_editor->invalidate();
}
void WritingTextState::drop()
{
m_editor->backToPreviousState();
m_editor->invalidate();
}
} // namespace app

View File

@ -0,0 +1,58 @@
// Aseprite
// Copyright (c) 2022-2024 Igara Studio S.A.
//
// This program is distributed under the terms of
// the End-User License Agreement for Aseprite.
#ifndef APP_UI_EDITOR_WRITING_TEXT_STATE_H_INCLUDED
#define APP_UI_EDITOR_WRITING_TEXT_STATE_H_INCLUDED
#pragma once
#include "app/ui/editor/standby_state.h"
#include <memory>
namespace app {
class CommandExecutionEvent;
class WritingTextState : public StandbyState {
public:
WritingTextState(Editor* editor,
const gfx::Rect& bounds);
~WritingTextState();
LeaveAction onLeaveState(Editor* editor, EditorState* newState) override;
void onEnterState(Editor* editor) override;
void onBeforePopState(Editor* editor) override;
void onEditorResize(Editor* editor) override;
bool onMouseDown(Editor* editor, ui::MouseMessage* msg) override;
bool onMouseUp(Editor* editor, ui::MouseMessage* msg) override;
bool onSetCursor(Editor* editor, const gfx::Point& mouseScreenPos) override;
bool onKeyDown(Editor* editor, ui::KeyMessage* msg) override;
bool onKeyUp(Editor* editor, ui::KeyMessage* msg) override;
private:
gfx::Rect calcEntryBounds();
void onBeforeCommandExecution(CommandExecutionEvent& ev);
void onFontChange();
void cancel();
void drop();
void switchCaretVisiblity();
class TextEditor;
Editor* m_editor;
gfx::Rect m_bounds;
std::unique_ptr<TextEditor> m_entry;
// True if the text was discarded.
bool m_discarded = false;
obs::scoped_connection m_beforeCmdConn;
obs::scoped_connection m_fontChangeConn;
};
} // namespace app
#endif // APP_UI_EDITOR_DRAWING_STATE_H_INCLUDED

View File

@ -16,6 +16,7 @@
#include "ui/display.h" #include "ui/display.h"
#include "ui/manager.h" #include "ui/manager.h"
#include "ui/message.h" #include "ui/message.h"
#include "ui/scale.h"
#include <cstdlib> #include <cstdlib>
@ -155,6 +156,8 @@ FontEntry::FontEntry()
addChild(&m_style); addChild(&m_style);
addChild(&m_antialias); addChild(&m_antialias);
m_face.setMinSize(gfx::Size(128*guiscale(), 0));
m_face.FontChange.connect([this](const FontInfo& newTypeName) { m_face.FontChange.connect([this](const FontInfo& newTypeName) {
setInfo(FontInfo(newTypeName, setInfo(FontInfo(newTypeName,
m_info.size(), m_info.size(),
@ -212,6 +215,12 @@ FontEntry::FontEntry()
}); });
} }
// Defined here as FontPopup type is not fully defined in the header
// file (and we have a std::unique_ptr<FontPopup> in FontEntry::FontFace).
FontEntry::~FontEntry()
{
}
void FontEntry::setInfo(const FontInfo& info, void FontEntry::setInfo(const FontInfo& info,
const From fromField) const From fromField)
{ {

View File

@ -31,6 +31,7 @@ namespace app {
}; };
FontEntry(); FontEntry();
~FontEntry();
FontInfo info() { return m_info; } FontInfo info() { return m_info; }
void setInfo(const FontInfo& info, From from = From::User); void setInfo(const FontInfo& info, From from = From::User);

View File

@ -125,10 +125,9 @@ private:
const auto* theme = app::skin::SkinTheme::get(this); const auto* theme = app::skin::SkinTheme::get(this);
try { try {
const text::FontMgrRef fontMgr = theme->fontMgr();
const gfx::Color color = theme->colors.text(); const gfx::Color color = theme->colors.text();
doc::ImageRef image = doc::ImageRef image =
render_text(fontMgr, m_fontInfo, text(), color); render_text(m_fontInfo, text(), color);
if (!image) if (!image)
return; return;
@ -330,12 +329,12 @@ void FontPopup::showPopup(Display* display,
m_listBox.selectChild(nullptr); m_listBox.selectChild(nullptr);
ui::fit_bounds(display, this, ui::fit_bounds(display, this,
gfx::Rect(buttonBounds.x, buttonBounds.y2(), 32, 32), gfx::Rect(buttonBounds.x, buttonBounds.y2(),
buttonBounds.w*2, buttonBounds.h),
[](const gfx::Rect& workarea, [](const gfx::Rect& workarea,
gfx::Rect& bounds, gfx::Rect& bounds,
std::function<gfx::Rect(Widget*)> getWidgetBounds) { std::function<gfx::Rect(Widget*)> getWidgetBounds) {
bounds.w = workarea.w / 2; bounds.h = workarea.y2() - bounds.y;
bounds.h = workarea.h / 2;
}); });
openWindow(); openWindow();

View File

@ -1141,17 +1141,21 @@ void SkinTheme::paintEntry(PaintEvent& ev)
gfx::Rect bounds = widget->clientBounds(); gfx::Rect bounds = widget->clientBounds();
// Outside borders // Outside borders
g->fillRect(BGCOLOR, bounds); const gfx::Color bg = BGCOLOR;
if (!is_transparent(bg))
g->fillRect(bg, bounds);
bool isMiniLook = false; bool isMiniLook = false;
auto skinPropery = std::static_pointer_cast<SkinProperty>(widget->getProperty(SkinProperty::Name)); auto skinPropery = std::static_pointer_cast<SkinProperty>(widget->getProperty(SkinProperty::Name));
if (skinPropery) if (skinPropery)
isMiniLook = (skinPropery->getLook() == MiniLook); isMiniLook = (skinPropery->getLook() == MiniLook);
drawRect(g, bounds, drawRect(
g, bounds,
(widget->hasFocus() ? (widget->hasFocus() ?
(isMiniLook ? parts.sunkenMiniFocused().get(): parts.sunkenFocused().get()): (isMiniLook ? parts.sunkenMiniFocused().get(): parts.sunkenFocused().get()):
(isMiniLook ? parts.sunkenMiniNormal().get() : parts.sunkenNormal().get()))); (isMiniLook ? parts.sunkenMiniNormal().get() : parts.sunkenNormal().get())),
!is_transparent(bg)); // Paint center if the BG is not transparent
drawEntryText(g, widget); drawEntryText(g, widget);
} }

View File

@ -65,26 +65,23 @@ private:
} // anonymous namespace } // anonymous namespace
doc::ImageRef render_text( text::FontRef get_font_from_info(
const text::FontMgrRef& fontMgr, const FontInfo& fontInfo)
const FontInfo& fontInfo,
const std::string& text,
gfx::Color color)
{ {
doc::ImageRef image;
auto* theme = skin::SkinTheme::instance(); auto* theme = skin::SkinTheme::instance();
ASSERT(theme); ASSERT(theme);
if (!theme) if (!theme)
return nullptr; return nullptr;
text::FontRef font; const text::FontMgrRef fontMgr = theme->fontMgr();
if (!fontMgr)
return nullptr;
text::FontRef font;
if (fontInfo.type() == FontInfo::Type::System) { if (fontInfo.type() == FontInfo::Type::System) {
// Just in case the typeface is not present in the FontInfo // Just in case the typeface is not present in the FontInfo
auto typeface = fontInfo.findTypeface(fontMgr); auto typeface = fontInfo.findTypeface(fontMgr);
const text::FontMgrRef fontMgr = theme->fontMgr();
font = fontMgr->makeFont(typeface); font = fontMgr->makeFont(typeface);
if (!fontInfo.useDefaultSize()) if (!fontInfo.useDefaultSize())
font->setSize(fontInfo.size()); font->setSize(fontInfo.size());
@ -98,50 +95,98 @@ doc::ImageRef render_text(
} }
} }
if (font)
font->setAntialias(fontInfo.antialias());
return font;
}
text::TextBlobRef create_text_blob(
const FontInfo& fontInfo,
const std::string& text)
{
const text::FontRef font = get_font_from_info(fontInfo);
if (!font) if (!font)
return nullptr; return nullptr;
font->setAntialias(fontInfo.antialias()); auto* theme = skin::SkinTheme::instance();
const text::FontMgrRef fontMgr = theme->fontMgr();
return text::TextBlob::MakeWithShaper(fontMgr, font, text);
}
doc::ImageRef render_text_blob(
const text::TextBlobRef& blob,
gfx::Color color)
{
ASSERT(blob != nullptr);
os::Paint paint;
// TODO offer Stroke, StrokeAndFill, and Fill styles
paint.style(os::Paint::Fill);
paint.color(color);
gfx::RectF bounds(0, 0, 1, 1);
blob->visitRuns([&bounds](text::TextBlob::RunInfo& run){
for (int i=0; i<run.glyphCount; ++i) {
bounds |= run.getGlyphBounds(i);
bounds |= gfx::RectF(0, 0, 1, run.font->metrics(nullptr));
}
});
if (bounds.w < 1) bounds.w = 1;
if (bounds.h < 1) bounds.h = 1;
doc::ImageRef image(
doc::Image::create(doc::IMAGE_RGB, bounds.w, bounds.h));
#ifdef LAF_SKIA
sk_sp<SkSurface> skSurface = wrap_docimage_in_sksurface(image.get());
os::SurfaceRef surface = base::make_ref<os::SkiaSurface>(skSurface);
text::draw_text(surface.get(), blob,
gfx::PointF(0, 0), &paint);
#endif // LAF_SKIA
return image;
}
doc::ImageRef render_text(
const FontInfo& fontInfo,
const std::string& text,
gfx::Color color)
{
const text::FontRef font = get_font_from_info(fontInfo);
if (!font)
return nullptr;
auto* theme = skin::SkinTheme::instance();
const text::FontMgrRef fontMgr = theme->fontMgr();
os::Paint paint; os::Paint paint;
paint.style(os::Paint::StrokeAndFill); paint.style(os::Paint::StrokeAndFill);
paint.color(color); paint.color(color);
text::TextBlobRef blob; // We have to measure all text runs which might use different
gfx::RectF bounds; // fonts (e.g. if the given font is not enough to shape other code
// points/languages).
MeasureHandler handler;
text::TextBlobRef blob =
text::TextBlob::MakeWithShaper(fontMgr, font, text, &handler);
if (!blob)
return nullptr;
if (fontInfo.type() == FontInfo::Type::System) { gfx::RectF bounds = handler.bounds();
// For native fonts we have to measure all text runs which might
// use different fonts (e.g. if the given font is not enough to
// shape other code points/languages).
MeasureHandler handler;
blob = text::TextBlob::MakeWithShaper(fontMgr, font, text, &handler);
bounds = handler.bounds();
}
else {
font->measureText(text, &bounds, &paint);
bounds.w += 1 + std::abs(bounds.x);
text::FontMetrics metrics;
bounds.h = font->metrics(&metrics);
}
if (bounds.w < 1) bounds.w = 1; if (bounds.w < 1) bounds.w = 1;
if (bounds.h < 1) bounds.h = 1; if (bounds.h < 1) bounds.h = 1;
image.reset(doc::Image::create(doc::IMAGE_RGB, bounds.w, bounds.h)); doc::ImageRef image(
doc::Image::create(doc::IMAGE_RGB, bounds.w, bounds.h));
#ifdef LAF_SKIA #ifdef LAF_SKIA
// Wrap the doc::Image into a os::Surface to render the text
sk_sp<SkSurface> skSurface = wrap_docimage_in_sksurface(image.get()); sk_sp<SkSurface> skSurface = wrap_docimage_in_sksurface(image.get());
os::SurfaceRef surface = base::make_ref<os::SkiaSurface>(skSurface); os::SurfaceRef surface = base::make_ref<os::SkiaSurface>(skSurface);
if (fontInfo.type() == FontInfo::Type::System) { text::draw_text(surface.get(), blob,
text::draw_text(surface.get(), blob, gfx::PointF(0, 0), &paint);
gfx::PointF(0, 0), &paint);
}
else {
text::draw_text(
surface.get(), fontMgr, font, text,
color, gfx::ColorNone, 0, 0, nullptr);
}
#endif // LAF_SKIA #endif // LAF_SKIA
return image; return image;

View File

@ -12,6 +12,7 @@
#include "doc/image_ref.h" #include "doc/image_ref.h"
#include "gfx/color.h" #include "gfx/color.h"
#include "text/font_mgr.h" #include "text/font_mgr.h"
#include "text/text_blob.h"
#include <string> #include <string>
@ -20,8 +21,18 @@ namespace app {
class Color; class Color;
class FontInfo; class FontInfo;
text::FontRef get_font_from_info(
const FontInfo& fontInfo);
text::TextBlobRef create_text_blob(
const FontInfo& fontInfo,
const std::string& text);
doc::ImageRef render_text_blob(
const text::TextBlobRef& blob,
gfx::Color color);
doc::ImageRef render_text( doc::ImageRef render_text(
const text::FontMgrRef& fontMgr,
const FontInfo& fontInfo, const FontInfo& fontInfo,
const std::string& text, const std::string& text,
gfx::Color color); gfx::Color color);

View File

@ -23,7 +23,7 @@ namespace render {
COMPOSITE, COMPOSITE,
// Composite the current cel two times (don't use the extral cel), // Composite the current cel two times (don't use the extral cel),
// but the second time using the extral blend mode. // but the second time using the extra cel blend mode.
OVER_COMPOSITE, OVER_COMPOSITE,
}; };

View File

@ -46,6 +46,7 @@ namespace ui {
void setCaretPos(int pos); void setCaretPos(int pos);
void setCaretToEnd(); void setCaretToEnd();
bool isCaretVisible() const { return !m_hidden && m_state; }
void selectText(int from, int to); void selectText(int from, int to);
void selectAllText(); void selectAllText();

View File

@ -1,5 +1,5 @@
// Aseprite UI Library // Aseprite UI Library
// Copyright (C) 2018-2022 Igara Studio S.A. // Copyright (C) 2018-2024 Igara Studio S.A.
// Copyright (C) 2001-2017 David Capello // Copyright (C) 2001-2017 David Capello
// //
// This file is released under the terms of the MIT license. // This file is released under the terms of the MIT license.
@ -193,7 +193,7 @@ Viewport* View::viewport()
return &m_viewport; return &m_viewport;
} }
Rect View::viewportBounds() Rect View::viewportBounds() const
{ {
return m_viewport.bounds() - m_viewport.border(); return m_viewport.bounds() - m_viewport.border();
} }

View File

@ -1,5 +1,5 @@
// Aseprite UI Library // Aseprite UI Library
// Copyright (C) 2019 Igara Studio S.A. // Copyright (C) 2019-2024 Igara Studio S.A.
// Copyright (C) 2001-2017 David Capello // Copyright (C) 2001-2017 David Capello
// //
// This file is released under the terms of the MIT license. // This file is released under the terms of the MIT license.
@ -54,7 +54,7 @@ namespace ui {
void updateView(const bool restoreScrollPos = true); void updateView(const bool restoreScrollPos = true);
Viewport* viewport(); Viewport* viewport();
gfx::Rect viewportBounds(); gfx::Rect viewportBounds() const;
// For viewable widgets // For viewable widgets
static View* getView(const Widget* viewableWidget); static View* getView(const Widget* viewableWidget);

View File

@ -174,6 +174,14 @@ text::Font* Widget::font() const
return m_font.get(); return m_font.get();
} }
void Widget::setFont(const text::FontRef& font)
{
if (m_font != font) {
m_font = font;
m_blob.reset();
}
}
void Widget::setBgColor(gfx::Color color) void Widget::setBgColor(gfx::Color color)
{ {
assert_ui_thread(); assert_ui_thread();

View File

@ -139,6 +139,7 @@ namespace ui {
// =============================================================== // ===============================================================
text::Font* font() const; text::Font* font() const;
void setFont(const text::FontRef& font);
// Gets the background color of the widget. // Gets the background color of the widget.
gfx::Color bgColor() const { gfx::Color bgColor() const {