mirror of
https://github.com/aseprite/aseprite.git
synced 2025-02-06 12:39:57 +00:00
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:
parent
ae091726fe
commit
0bf9353a02
Binary file not shown.
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 17 KiB |
@ -414,6 +414,7 @@
|
||||
<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_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_jumble" x="176" y="112" 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 |
@ -410,6 +410,7 @@
|
||||
<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_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_jumble" x="176" y="112" w="16" h="16" />
|
||||
<part id="tool_configuration" x="144" y="128" w="16" h="16" />
|
||||
|
13
data/gui.xml
13
data/gui.xml
@ -66,7 +66,6 @@
|
||||
<key command="Outline" shortcut="Shift+O" />
|
||||
<key command="ColorCurve" shortcut="Ctrl+M" />
|
||||
<key command="ColorCurve" shortcut="F10" />
|
||||
<key command="PasteText" shortcut="T" />
|
||||
<key command="Options" mac="Cmd+," />
|
||||
<key command="Options" shortcut="Ctrl+K" mac="Cmd+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="jumble" shortcut="R" />
|
||||
|
||||
<key tool="text" shortcut="T" />
|
||||
</tools>
|
||||
|
||||
<!-- Editor Quicktools: these are modifiers to select quickly a
|
||||
@ -1506,6 +1507,16 @@
|
||||
default_brush_size="16" />
|
||||
</group>
|
||||
|
||||
<group id="text">
|
||||
<tool id="text"
|
||||
text="@.text"
|
||||
ink="text"
|
||||
controller="two_points"
|
||||
pointshape="pixel"
|
||||
intertwine="as_rectangles"
|
||||
traceepolicy="last" />
|
||||
</group>
|
||||
|
||||
</tools>
|
||||
|
||||
</gui>
|
||||
|
@ -1811,6 +1811,7 @@ contour = Contour Tool
|
||||
polygon = Polygon Tool
|
||||
blur = Blur Tool
|
||||
jumble = Jumble Tool
|
||||
text = Text Tool
|
||||
shortcut = Shortcut: {0}
|
||||
preview_hide = Hide Preview
|
||||
preview_show = Show Preview
|
||||
|
@ -403,9 +403,11 @@ if(ENABLE_UI)
|
||||
ui/editor/play_state.cpp
|
||||
ui/editor/scrolling_state.cpp
|
||||
ui/editor/select_box_state.cpp
|
||||
ui/editor/select_text_box_state.cpp
|
||||
ui/editor/standby_state.cpp
|
||||
ui/editor/state_with_wheel_behavior.cpp
|
||||
ui/editor/transform_handles.cpp
|
||||
ui/editor/writing_text_state.cpp
|
||||
ui/editor/zooming_state.cpp
|
||||
ui/export_file_window.cpp
|
||||
ui/expr_entry.cpp
|
||||
|
@ -16,7 +16,6 @@
|
||||
#include "app/pref/preferences.h"
|
||||
#include "app/ui/drop_down_button.h"
|
||||
#include "app/ui/editor/editor.h"
|
||||
#include "app/ui/skin/skin_theme.h"
|
||||
#include "app/ui/timeline/timeline.h"
|
||||
#include "app/util/render_text.h"
|
||||
#include "base/fs.h"
|
||||
@ -75,21 +74,7 @@ void PasteTextCommand::onExecute(Context* ctx)
|
||||
return;
|
||||
|
||||
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());
|
||||
}
|
||||
|
||||
FontInfo fontInfo = FontInfo::getFromPreferences();
|
||||
PasteTextWindow window(fontInfo, pref.colorBar.fgColor());
|
||||
|
||||
window.userText()->setText(last_text_used);
|
||||
@ -101,20 +86,14 @@ void PasteTextCommand::onExecute(Context* ctx)
|
||||
last_text_used = window.userText()->text();
|
||||
|
||||
fontInfo = window.fontInfo();
|
||||
pref.textTool.fontInfo(base::convert_to<std::string>(fontInfo));
|
||||
if (!pref.textTool.fontFace().empty()) {
|
||||
pref.textTool.fontFace.clearValue();
|
||||
pref.textTool.fontSize.clearValue();
|
||||
pref.textTool.antialias.clearValue();
|
||||
}
|
||||
fontInfo.updatePreferences();
|
||||
|
||||
try {
|
||||
auto* theme = skin::SkinTheme::instance();
|
||||
std::string text = window.userText()->text();
|
||||
app::Color color = window.fontColor()->getColor();
|
||||
|
||||
doc::ImageRef image = render_text(
|
||||
theme->fontMgr(), fontInfo, text,
|
||||
fontInfo, text,
|
||||
gfx::rgba(color.getRed(),
|
||||
color.getGreen(),
|
||||
color.getBlue(),
|
||||
|
@ -9,6 +9,7 @@
|
||||
#endif
|
||||
|
||||
#include "app/font_info.h"
|
||||
#include "app/pref/preferences.h"
|
||||
#include "base/fs.h"
|
||||
#include "base/split_string.h"
|
||||
#include "fmt/format.h"
|
||||
@ -76,6 +77,39 @@ text::TypefaceRef FontInfo::findTypeface(const text::FontMgrRef& fontMgr) const
|
||||
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 base {
|
||||
|
@ -61,6 +61,9 @@ namespace app {
|
||||
|
||||
text::TypefaceRef findTypeface(const text::FontMgrRef& fontMgr) const;
|
||||
|
||||
static FontInfo getFromPreferences();
|
||||
void updatePreferences();
|
||||
|
||||
bool operator==(const FontInfo& other) const {
|
||||
return (m_type == other.m_type &&
|
||||
m_name == other.m_name &&
|
||||
|
@ -1,5 +1,5 @@
|
||||
// Aseprite
|
||||
// Copyright (C) 2018-2020 Igara Studio S.A.
|
||||
// Copyright (C) 2018-2022 Igara Studio S.A.
|
||||
// Copyright (C) 2001-2017 David Capello
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
@ -68,6 +68,9 @@ namespace app {
|
||||
// Returns true if this ink is used to mark slices
|
||||
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
|
||||
virtual bool withDitheringOptions() const { return false; }
|
||||
|
||||
|
@ -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 app
|
||||
|
@ -75,6 +75,7 @@ const char* WellKnownInks::Slice = "slice";
|
||||
const char* WellKnownInks::MoveSlice = "move_slice";
|
||||
const char* WellKnownInks::Blur = "blur";
|
||||
const char* WellKnownInks::Jumble = "jumble";
|
||||
const char* WellKnownInks::Text = "text";
|
||||
|
||||
const char* WellKnownControllers::Freehand = "freehand";
|
||||
const char* WellKnownControllers::PointByPoint = "point_by_point";
|
||||
@ -135,6 +136,7 @@ ToolBox::ToolBox()
|
||||
m_inks[WellKnownInks::Slice] = new SliceInk();
|
||||
m_inks[WellKnownInks::Blur] = new BlurInk();
|
||||
m_inks[WellKnownInks::Jumble] = new JumbleInk();
|
||||
m_inks[WellKnownInks::Text] = new TextInk();
|
||||
|
||||
m_controllers[WellKnownControllers::Freehand] = new FreehandController();
|
||||
m_controllers[WellKnownControllers::PointByPoint] = new PointByPointController();
|
||||
|
@ -56,6 +56,7 @@ namespace app {
|
||||
extern const char* MoveSlice;
|
||||
extern const char* Blur;
|
||||
extern const char* Jumble;
|
||||
extern const char* Text;
|
||||
};
|
||||
|
||||
namespace WellKnownControllers {
|
||||
|
@ -43,6 +43,7 @@
|
||||
#include "app/ui/dynamics_popup.h"
|
||||
#include "app/ui/editor/editor.h"
|
||||
#include "app/ui/expr_entry.h"
|
||||
#include "app/ui/font_entry.h"
|
||||
#include "app/ui/icon_button.h"
|
||||
#include "app/ui/keyboard_shortcuts.h"
|
||||
#include "app/ui/layer_frame_comboboxes.h"
|
||||
@ -1837,6 +1838,22 @@ private:
|
||||
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,
|
||||
ColorBar* colorBar)
|
||||
{
|
||||
@ -1891,6 +1908,7 @@ ContextBar::ContextBar(TooltipManager* tooltipManager,
|
||||
m_symmetry->setVisible(pref.symmetryMode.enabled());
|
||||
|
||||
addChild(m_sliceFields = new SliceFields);
|
||||
addChild(m_fontSelector = new FontSelector(this));
|
||||
|
||||
setupTooltips(tooltipManager);
|
||||
|
||||
@ -2183,6 +2201,11 @@ void ContextBar::updateForTool(tools::Tool* tool)
|
||||
(tool->getInk(0)->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
|
||||
const bool isFloodfill = tool &&
|
||||
(tool->getPointShape(0)->isFloodFill() ||
|
||||
@ -2267,6 +2290,8 @@ void ContextBar::updateForTool(tools::Tool* tool)
|
||||
if (isSlice)
|
||||
updateSliceFields(UIContext::instance()->activeSite());
|
||||
|
||||
m_fontSelector->setVisible(isText);
|
||||
|
||||
// Update ink shades with the current selected palette entries
|
||||
if (updateShade)
|
||||
m_inkShades->updateShadeFromColorBarPicks();
|
||||
@ -2554,6 +2579,11 @@ void ContextBar::setInkType(tools::InkType type)
|
||||
m_inkType->setInkType(type);
|
||||
}
|
||||
|
||||
FontInfo ContextBar::fontInfo() const
|
||||
{
|
||||
return m_fontSelector->info();
|
||||
}
|
||||
|
||||
render::DitheringMatrix ContextBar::ditheringMatrix()
|
||||
{
|
||||
return m_ditheringSelector->ditheringMatrix();
|
||||
|
@ -1,5 +1,5 @@
|
||||
// Aseprite
|
||||
// Copyright (C) 2018-2022 Igara Studio S.A.
|
||||
// Copyright (C) 2018-2024 Igara Studio S.A.
|
||||
// Copyright (C) 2001-2017 David Capello
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
@ -52,6 +52,7 @@ namespace app {
|
||||
class BrushSlot;
|
||||
class ColorBar;
|
||||
class DitheringSelector;
|
||||
class FontInfo;
|
||||
class GradientTypeSelector;
|
||||
class SamplingSelector;
|
||||
class Transformation;
|
||||
@ -89,6 +90,9 @@ namespace app {
|
||||
|
||||
void setInkType(tools::InkType type);
|
||||
|
||||
// For text tool
|
||||
FontInfo fontInfo() const;
|
||||
|
||||
// For gradients
|
||||
render::DitheringMatrix ditheringMatrix();
|
||||
render::DitheringAlgorithmBase* ditheringAlgorithm();
|
||||
@ -99,6 +103,7 @@ namespace app {
|
||||
|
||||
// Signals
|
||||
obs::signal<void()> BrushChange;
|
||||
obs::signal<void()> FontChange;
|
||||
|
||||
protected:
|
||||
void onInitTheme(ui::InitThemeEvent& ev) override;
|
||||
@ -163,6 +168,7 @@ namespace app {
|
||||
class AutoSelectLayerField;
|
||||
class SymmetryField;
|
||||
class SliceFields;
|
||||
class FontSelector;
|
||||
|
||||
ZoomButtons* m_zoomButtons;
|
||||
SamplingSelector* m_samplingSelector;
|
||||
@ -201,6 +207,7 @@ namespace app {
|
||||
ui::Label* m_selectBoxHelp;
|
||||
SymmetryField* m_symmetry;
|
||||
SliceFields* m_sliceFields;
|
||||
FontSelector* m_fontSelector = nullptr;
|
||||
obs::scoped_connection m_symmModeConn;
|
||||
obs::scoped_connection m_fgColorConn;
|
||||
obs::scoped_connection m_bgColorConn;
|
||||
|
@ -2237,6 +2237,9 @@ void Editor::onResize(ui::ResizeEvent& ev)
|
||||
{
|
||||
Widget::onResize(ev);
|
||||
m_padding = calcExtraPadding(m_proj);
|
||||
|
||||
if (m_state)
|
||||
m_state->onEditorResize(this);
|
||||
}
|
||||
|
||||
void Editor::onPaint(ui::PaintEvent& ev)
|
||||
|
@ -83,6 +83,11 @@ namespace app {
|
||||
// Called when the editor gets the focus.
|
||||
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.
|
||||
virtual bool onMouseDown(Editor* editor, ui::MouseMessage* msg) { return false; }
|
||||
|
||||
|
71
src/app/ui/editor/select_text_box_state.cpp
Normal file
71
src/app/ui/editor/select_text_box_state.cpp
Normal 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
|
35
src/app/ui/editor/select_text_box_state.h
Normal file
35
src/app/ui/editor/select_text_box_state.h
Normal 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
|
@ -41,6 +41,7 @@
|
||||
#include "app/ui/editor/pivot_helpers.h"
|
||||
#include "app/ui/editor/pixels_movement.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/transform_handles.h"
|
||||
#include "app/ui/editor/vec2.h"
|
||||
@ -229,6 +230,7 @@ bool StandbyState::onMouseDown(Editor* editor, MouseMessage* msg)
|
||||
return true;
|
||||
}
|
||||
|
||||
// Handle Slice tool
|
||||
if (clickedInk->isSlice()) {
|
||||
EditorHit hit = editor->calcHit(msg->position());
|
||||
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
|
||||
// possibility to transform/move the selection. In other case,
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
else if (ink->isText()) {
|
||||
editor->showMouseCursor(kCrosshairCursor);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return StateWithWheelBehavior::onSetCursor(editor, mouseScreenPos);
|
||||
|
349
src/app/ui/editor/writing_text_state.cpp
Normal file
349
src/app/ui/editor/writing_text_state.cpp
Normal 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
|
58
src/app/ui/editor/writing_text_state.h
Normal file
58
src/app/ui/editor/writing_text_state.h
Normal 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
|
@ -16,6 +16,7 @@
|
||||
#include "ui/display.h"
|
||||
#include "ui/manager.h"
|
||||
#include "ui/message.h"
|
||||
#include "ui/scale.h"
|
||||
|
||||
#include <cstdlib>
|
||||
|
||||
@ -155,6 +156,8 @@ FontEntry::FontEntry()
|
||||
addChild(&m_style);
|
||||
addChild(&m_antialias);
|
||||
|
||||
m_face.setMinSize(gfx::Size(128*guiscale(), 0));
|
||||
|
||||
m_face.FontChange.connect([this](const FontInfo& newTypeName) {
|
||||
setInfo(FontInfo(newTypeName,
|
||||
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,
|
||||
const From fromField)
|
||||
{
|
||||
|
@ -31,6 +31,7 @@ namespace app {
|
||||
};
|
||||
|
||||
FontEntry();
|
||||
~FontEntry();
|
||||
|
||||
FontInfo info() { return m_info; }
|
||||
void setInfo(const FontInfo& info, From from = From::User);
|
||||
|
@ -125,10 +125,9 @@ private:
|
||||
const auto* theme = app::skin::SkinTheme::get(this);
|
||||
|
||||
try {
|
||||
const text::FontMgrRef fontMgr = theme->fontMgr();
|
||||
const gfx::Color color = theme->colors.text();
|
||||
doc::ImageRef image =
|
||||
render_text(fontMgr, m_fontInfo, text(), color);
|
||||
render_text(m_fontInfo, text(), color);
|
||||
if (!image)
|
||||
return;
|
||||
|
||||
@ -330,12 +329,12 @@ void FontPopup::showPopup(Display* display,
|
||||
m_listBox.selectChild(nullptr);
|
||||
|
||||
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,
|
||||
gfx::Rect& bounds,
|
||||
std::function<gfx::Rect(Widget*)> getWidgetBounds) {
|
||||
bounds.w = workarea.w / 2;
|
||||
bounds.h = workarea.h / 2;
|
||||
bounds.h = workarea.y2() - bounds.y;
|
||||
});
|
||||
|
||||
openWindow();
|
||||
|
@ -1141,17 +1141,21 @@ void SkinTheme::paintEntry(PaintEvent& ev)
|
||||
gfx::Rect bounds = widget->clientBounds();
|
||||
|
||||
// Outside borders
|
||||
g->fillRect(BGCOLOR, bounds);
|
||||
const gfx::Color bg = BGCOLOR;
|
||||
if (!is_transparent(bg))
|
||||
g->fillRect(bg, bounds);
|
||||
|
||||
bool isMiniLook = false;
|
||||
auto skinPropery = std::static_pointer_cast<SkinProperty>(widget->getProperty(SkinProperty::Name));
|
||||
if (skinPropery)
|
||||
isMiniLook = (skinPropery->getLook() == MiniLook);
|
||||
|
||||
drawRect(g, bounds,
|
||||
drawRect(
|
||||
g, bounds,
|
||||
(widget->hasFocus() ?
|
||||
(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);
|
||||
}
|
||||
|
@ -65,26 +65,23 @@ private:
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
doc::ImageRef render_text(
|
||||
const text::FontMgrRef& fontMgr,
|
||||
const FontInfo& fontInfo,
|
||||
const std::string& text,
|
||||
gfx::Color color)
|
||||
text::FontRef get_font_from_info(
|
||||
const FontInfo& fontInfo)
|
||||
{
|
||||
doc::ImageRef image;
|
||||
|
||||
auto* theme = skin::SkinTheme::instance();
|
||||
ASSERT(theme);
|
||||
if (!theme)
|
||||
return nullptr;
|
||||
|
||||
text::FontRef font;
|
||||
const text::FontMgrRef fontMgr = theme->fontMgr();
|
||||
if (!fontMgr)
|
||||
return nullptr;
|
||||
|
||||
text::FontRef font;
|
||||
if (fontInfo.type() == FontInfo::Type::System) {
|
||||
// Just in case the typeface is not present in the FontInfo
|
||||
auto typeface = fontInfo.findTypeface(fontMgr);
|
||||
|
||||
const text::FontMgrRef fontMgr = theme->fontMgr();
|
||||
font = fontMgr->makeFont(typeface);
|
||||
if (!fontInfo.useDefaultSize())
|
||||
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)
|
||||
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;
|
||||
paint.style(os::Paint::StrokeAndFill);
|
||||
paint.color(color);
|
||||
|
||||
text::TextBlobRef blob;
|
||||
gfx::RectF bounds;
|
||||
// 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;
|
||||
text::TextBlobRef blob =
|
||||
text::TextBlob::MakeWithShaper(fontMgr, font, text, &handler);
|
||||
if (!blob)
|
||||
return nullptr;
|
||||
|
||||
if (fontInfo.type() == FontInfo::Type::System) {
|
||||
// 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);
|
||||
}
|
||||
gfx::RectF bounds = handler.bounds();
|
||||
if (bounds.w < 1) bounds.w = 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
|
||||
// Wrap the doc::Image into a os::Surface to render the text
|
||||
sk_sp<SkSurface> skSurface = wrap_docimage_in_sksurface(image.get());
|
||||
os::SurfaceRef surface = base::make_ref<os::SkiaSurface>(skSurface);
|
||||
if (fontInfo.type() == FontInfo::Type::System) {
|
||||
text::draw_text(surface.get(), blob,
|
||||
gfx::PointF(0, 0), &paint);
|
||||
}
|
||||
else {
|
||||
text::draw_text(
|
||||
surface.get(), fontMgr, font, text,
|
||||
color, gfx::ColorNone, 0, 0, nullptr);
|
||||
}
|
||||
text::draw_text(surface.get(), blob,
|
||||
gfx::PointF(0, 0), &paint);
|
||||
#endif // LAF_SKIA
|
||||
|
||||
return image;
|
||||
|
@ -12,6 +12,7 @@
|
||||
#include "doc/image_ref.h"
|
||||
#include "gfx/color.h"
|
||||
#include "text/font_mgr.h"
|
||||
#include "text/text_blob.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
@ -20,8 +21,18 @@ namespace app {
|
||||
class Color;
|
||||
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(
|
||||
const text::FontMgrRef& fontMgr,
|
||||
const FontInfo& fontInfo,
|
||||
const std::string& text,
|
||||
gfx::Color color);
|
||||
|
@ -23,7 +23,7 @@ namespace render {
|
||||
COMPOSITE,
|
||||
|
||||
// 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,
|
||||
};
|
||||
|
||||
|
@ -46,6 +46,7 @@ namespace ui {
|
||||
|
||||
void setCaretPos(int pos);
|
||||
void setCaretToEnd();
|
||||
bool isCaretVisible() const { return !m_hidden && m_state; }
|
||||
|
||||
void selectText(int from, int to);
|
||||
void selectAllText();
|
||||
|
@ -1,5 +1,5 @@
|
||||
// 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
|
||||
//
|
||||
// This file is released under the terms of the MIT license.
|
||||
@ -193,7 +193,7 @@ Viewport* View::viewport()
|
||||
return &m_viewport;
|
||||
}
|
||||
|
||||
Rect View::viewportBounds()
|
||||
Rect View::viewportBounds() const
|
||||
{
|
||||
return m_viewport.bounds() - m_viewport.border();
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
// Aseprite UI Library
|
||||
// Copyright (C) 2019 Igara Studio S.A.
|
||||
// Copyright (C) 2019-2024 Igara Studio S.A.
|
||||
// Copyright (C) 2001-2017 David Capello
|
||||
//
|
||||
// This file is released under the terms of the MIT license.
|
||||
@ -54,7 +54,7 @@ namespace ui {
|
||||
void updateView(const bool restoreScrollPos = true);
|
||||
|
||||
Viewport* viewport();
|
||||
gfx::Rect viewportBounds();
|
||||
gfx::Rect viewportBounds() const;
|
||||
|
||||
// For viewable widgets
|
||||
static View* getView(const Widget* viewableWidget);
|
||||
|
@ -174,6 +174,14 @@ text::Font* Widget::font() const
|
||||
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)
|
||||
{
|
||||
assert_ui_thread();
|
||||
|
@ -139,6 +139,7 @@ namespace ui {
|
||||
// ===============================================================
|
||||
|
||||
text::Font* font() const;
|
||||
void setFont(const text::FontRef& font);
|
||||
|
||||
// Gets the background color of the widget.
|
||||
gfx::Color bgColor() const {
|
||||
|
Loading…
x
Reference in New Issue
Block a user