mirror of
https://github.com/luanti-org/luanti.git
synced 2025-07-22 17:18:39 +00:00
Add preliminary text support
This commit is contained in:
parent
bb2f857b04
commit
c7fca1b956
11 changed files with 323 additions and 3 deletions
|
@ -45,6 +45,8 @@ function ui.Elem:new(param)
|
||||||
end
|
end
|
||||||
|
|
||||||
function ui.Elem:_init(props)
|
function ui.Elem:_init(props)
|
||||||
|
self._label = ui._opt(props.label, "string")
|
||||||
|
|
||||||
self._groups = {}
|
self._groups = {}
|
||||||
self._children = {}
|
self._children = {}
|
||||||
|
|
||||||
|
@ -114,6 +116,10 @@ function ui.Elem:_encode_fields()
|
||||||
ui._encode_flag(fl, "Z", ui._encode_array("z", child_ids))
|
ui._encode_flag(fl, "Z", ui._encode_array("z", child_ids))
|
||||||
end
|
end
|
||||||
|
|
||||||
|
if ui._shift_flag(fl, self._label) then
|
||||||
|
ui._encode_flag(fl, "s", self._label)
|
||||||
|
end
|
||||||
|
|
||||||
self:_encode_box(fl, self._boxes.main)
|
self:_encode_box(fl, self._boxes.main)
|
||||||
|
|
||||||
return ui._encode_flags(fl)
|
return ui._encode_flags(fl)
|
||||||
|
|
|
@ -82,6 +82,18 @@ local icon_place_map = {
|
||||||
bottom = 4,
|
bottom = 4,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
local align_map = {
|
||||||
|
left = 0,
|
||||||
|
center = 1,
|
||||||
|
right = 2,
|
||||||
|
}
|
||||||
|
|
||||||
|
local valign_map = {
|
||||||
|
top = 0,
|
||||||
|
center = 1,
|
||||||
|
bottom = 2,
|
||||||
|
}
|
||||||
|
|
||||||
local function opt_color(val, def)
|
local function opt_color(val, def)
|
||||||
assert(val == nil or core.colorspec_to_int(val))
|
assert(val == nil or core.colorspec_to_int(val))
|
||||||
return val or def
|
return val or def
|
||||||
|
@ -130,6 +142,22 @@ local function cascade_layer(new, add, props, p)
|
||||||
ui._opt(add[p.."_frame_time"], "number", props[p.."_frame_time"])
|
ui._opt(add[p.."_frame_time"], "number", props[p.."_frame_time"])
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local function cascade_text(new, add, props)
|
||||||
|
new.prepend = ui._opt(add.prepend, "string", props.prepend)
|
||||||
|
new.append = ui._opt(add.append, "string", props.append)
|
||||||
|
|
||||||
|
new.text_color = opt_color(add.text_color, props.text_color)
|
||||||
|
new.text_mark = opt_color(add.text_mark, props.text_mark)
|
||||||
|
new.text_size = ui._opt(add.text_size, "number", props.text_size)
|
||||||
|
|
||||||
|
new.text_mono = ui._opt(add.text_mono, "boolean", props.text_mono)
|
||||||
|
new.text_italic = ui._opt(add.text_italic, "boolean", props.text_italic)
|
||||||
|
new.text_bold = ui._opt(add.text_bold, "boolean", props.text_bold)
|
||||||
|
|
||||||
|
new.text_align = ui._opt_enum(add.text_align, align_map, props.text_align)
|
||||||
|
new.text_valign = ui._opt_enum(add.text_valign, valign_map, props.text_valign)
|
||||||
|
end
|
||||||
|
|
||||||
function ui._cascade_props(add, props)
|
function ui._cascade_props(add, props)
|
||||||
local new = {}
|
local new = {}
|
||||||
|
|
||||||
|
@ -148,6 +176,8 @@ function ui._cascade_props(add, props)
|
||||||
new.icon_gutter = ui._opt(add.icon_gutter, "number", props.icon_gutter)
|
new.icon_gutter = ui._opt(add.icon_gutter, "number", props.icon_gutter)
|
||||||
new.icon_overlap = ui._opt(add.icon_overlap, "boolean", props.icon_overlap)
|
new.icon_overlap = ui._opt(add.icon_overlap, "boolean", props.icon_overlap)
|
||||||
|
|
||||||
|
cascade_text(new, add, props)
|
||||||
|
|
||||||
return new
|
return new
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -243,6 +273,40 @@ local function encode_layer(props, p)
|
||||||
return fl
|
return fl
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local function encode_text(props)
|
||||||
|
local fl = ui._make_flags()
|
||||||
|
|
||||||
|
if ui._shift_flag(fl, props.prepend) then
|
||||||
|
ui._encode_flag(fl, "s", props.prepend)
|
||||||
|
end
|
||||||
|
if ui._shift_flag(fl, props.append) then
|
||||||
|
ui._encode_flag(fl, "s", props.append)
|
||||||
|
end
|
||||||
|
|
||||||
|
if ui._shift_flag(fl, props.text_color) then
|
||||||
|
ui._encode_flag(fl, "I", core.colorspec_to_int(props.text_color))
|
||||||
|
end
|
||||||
|
if ui._shift_flag(fl, props.text_mark) then
|
||||||
|
ui._encode_flag(fl, "I", core.colorspec_to_int(props.text_mark))
|
||||||
|
end
|
||||||
|
if ui._shift_flag(fl, props.text_size) then
|
||||||
|
ui._encode_flag(fl, "I", props.text_size)
|
||||||
|
end
|
||||||
|
|
||||||
|
ui._shift_flag_bool(fl, props.text_mono)
|
||||||
|
ui._shift_flag_bool(fl, props.text_italic)
|
||||||
|
ui._shift_flag_bool(fl, props.text_bold)
|
||||||
|
|
||||||
|
if ui._shift_flag(fl, props.text_align) then
|
||||||
|
ui._encode_flag(fl, "B", align_map[props.text_align])
|
||||||
|
end
|
||||||
|
if ui._shift_flag(fl, props.text_valign) then
|
||||||
|
ui._encode_flag(fl, "B", valign_map[props.text_valign])
|
||||||
|
end
|
||||||
|
|
||||||
|
return fl
|
||||||
|
end
|
||||||
|
|
||||||
local function encode_subflags(fl, sub_fl)
|
local function encode_subflags(fl, sub_fl)
|
||||||
if ui._shift_flag(fl, sub_fl.flags ~= 0) then
|
if ui._shift_flag(fl, sub_fl.flags ~= 0) then
|
||||||
ui._encode_flag(fl, "s", ui._encode_flags(sub_fl))
|
ui._encode_flag(fl, "s", ui._encode_flags(sub_fl))
|
||||||
|
@ -277,5 +341,7 @@ function ui._encode_props(props)
|
||||||
end
|
end
|
||||||
ui._shift_flag_bool(fl, props.icon_overlap)
|
ui._shift_flag_bool(fl, props.icon_overlap)
|
||||||
|
|
||||||
|
encode_subflags(fl, encode_text(props))
|
||||||
|
|
||||||
return ui._encode("s", ui._encode_flags(fl))
|
return ui._encode("s", ui._encode_flags(fl))
|
||||||
end
|
end
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
#include "debug.h"
|
#include "debug.h"
|
||||||
#include "log.h"
|
#include "log.h"
|
||||||
#include "porting.h"
|
#include "porting.h"
|
||||||
|
#include "client/fontengine.h"
|
||||||
#include "ui/elem.h"
|
#include "ui/elem.h"
|
||||||
#include "ui/manager.h"
|
#include "ui/manager.h"
|
||||||
#include "ui/window.h"
|
#include "ui/window.h"
|
||||||
|
@ -29,11 +30,16 @@ namespace ui
|
||||||
void Box::reset()
|
void Box::reset()
|
||||||
{
|
{
|
||||||
m_content.clear();
|
m_content.clear();
|
||||||
|
m_label = "";
|
||||||
|
|
||||||
m_style.reset();
|
m_style.reset();
|
||||||
|
|
||||||
for (State i = 0; i < m_style_refs.size(); i++) {
|
for (State i = 0; i < m_style_refs.size(); i++) {
|
||||||
m_style_refs[i] = NO_STYLE;
|
m_style_refs[i] = NO_STYLE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
m_text = L"";
|
||||||
|
m_font = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Box::read(std::istream &full_is)
|
void Box::read(std::istream &full_is)
|
||||||
|
@ -93,6 +99,15 @@ namespace ui
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Now that we have updated text style properties, we can update our
|
||||||
|
// cached text string and font object.
|
||||||
|
m_text = utf8_to_wide(m_style.text.prepend) + utf8_to_wide(m_label) +
|
||||||
|
utf8_to_wide(m_style.text.append);
|
||||||
|
|
||||||
|
FontSpec spec(m_style.text.size, m_style.text.mono ? FM_Mono : FM_Standard,
|
||||||
|
m_style.text.bold, m_style.text.italic);
|
||||||
|
m_font = g_fontengine->getFont(spec);
|
||||||
|
|
||||||
// Since our box has been restyled, the previously computed layout
|
// Since our box has been restyled, the previously computed layout
|
||||||
// information is no longer valid.
|
// information is no longer valid.
|
||||||
m_min_layout = SizeF();
|
m_min_layout = SizeF();
|
||||||
|
@ -140,7 +155,7 @@ namespace ui
|
||||||
{
|
{
|
||||||
if (m_style.display != DisplayMode::HIDDEN) {
|
if (m_style.display != DisplayMode::HIDDEN) {
|
||||||
drawBox();
|
drawBox();
|
||||||
drawIcon();
|
drawItems();
|
||||||
}
|
}
|
||||||
|
|
||||||
for (Box *box : m_content) {
|
for (Box *box : m_content) {
|
||||||
|
@ -294,6 +309,11 @@ namespace ui
|
||||||
|
|
||||||
void Box::resizeBox()
|
void Box::resizeBox()
|
||||||
{
|
{
|
||||||
|
// First, we need to expand the minimum size of the box to accommodate
|
||||||
|
// the size of any text it might contain.
|
||||||
|
SizeF text_size = getWindow().getTextSize(m_font, m_text);
|
||||||
|
m_min_content = m_min_content.max(text_size);
|
||||||
|
|
||||||
// If the box is set to clip its contents in either dimension, we can
|
// If the box is set to clip its contents in either dimension, we can
|
||||||
// set the minimum content size to zero for that coordinate.
|
// set the minimum content size to zero for that coordinate.
|
||||||
if (m_style.layout.clip == DirFlags::X || m_style.layout.clip == DirFlags::BOTH) {
|
if (m_style.layout.clip == DirFlags::X || m_style.layout.clip == DirFlags::BOTH) {
|
||||||
|
@ -627,13 +647,19 @@ namespace ui
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Box::drawIcon()
|
void Box::drawItems()
|
||||||
{
|
{
|
||||||
// The icon rect is computed while the box is being laid out, so we
|
// The icon rect is computed while the box is being laid out, so we
|
||||||
// just need to draw it with the fill color behind it.
|
// just need to draw it with the fill color behind it.
|
||||||
getWindow().drawRect(m_icon_rect, m_clip_rect, m_style.icon.fill);
|
getWindow().drawRect(m_icon_rect, m_clip_rect, m_style.icon.fill);
|
||||||
getWindow().drawTexture(m_icon_rect, m_clip_rect, m_style.icon.image,
|
getWindow().drawTexture(m_icon_rect, m_clip_rect, m_style.icon.image,
|
||||||
getLayerSource(m_style.icon), m_style.icon.tint);
|
getLayerSource(m_style.icon), m_style.icon.tint);
|
||||||
|
|
||||||
|
// The window handles all the complicated text layout, so we can just
|
||||||
|
// draw the text with all the appropriate styling.
|
||||||
|
getWindow().drawText(m_content_rect, m_clip_rect, m_font, m_text,
|
||||||
|
m_style.text.color, m_style.text.mark,
|
||||||
|
m_style.text.align, m_style.text.valign);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Box::isHovered() const
|
bool Box::isHovered() const
|
||||||
|
|
10
src/ui/box.h
10
src/ui/box.h
|
@ -58,10 +58,15 @@ namespace ui
|
||||||
u32 m_item;
|
u32 m_item;
|
||||||
|
|
||||||
std::vector<Box *> m_content;
|
std::vector<Box *> m_content;
|
||||||
|
std::string_view m_label;
|
||||||
|
|
||||||
Style m_style;
|
Style m_style;
|
||||||
std::array<u32, NUM_STATES> m_style_refs;
|
std::array<u32, NUM_STATES> m_style_refs;
|
||||||
|
|
||||||
|
// We cache the font and text content every time the box is restyled.
|
||||||
|
std::wstring m_text;
|
||||||
|
gui::IGUIFont *m_font;
|
||||||
|
|
||||||
// Cached information about the layout of the box, which is cleared in
|
// Cached information about the layout of the box, which is cleared in
|
||||||
// restyle() and recomputed in resize() and relayout().
|
// restyle() and recomputed in resize() and relayout().
|
||||||
SizeF m_min_layout;
|
SizeF m_min_layout;
|
||||||
|
@ -97,6 +102,9 @@ namespace ui
|
||||||
const std::vector<Box *> &getContent() const { return m_content; }
|
const std::vector<Box *> &getContent() const { return m_content; }
|
||||||
void setContent(std::vector<Box *> content) { m_content = std::move(content); }
|
void setContent(std::vector<Box *> content) { m_content = std::move(content); }
|
||||||
|
|
||||||
|
std::string_view getLabel() const { return m_label; }
|
||||||
|
void setLabel(std::string_view label) { m_label = label; }
|
||||||
|
|
||||||
void reset();
|
void reset();
|
||||||
void read(std::istream &is);
|
void read(std::istream &is);
|
||||||
|
|
||||||
|
@ -125,7 +133,7 @@ namespace ui
|
||||||
void relayoutPlace();
|
void relayoutPlace();
|
||||||
|
|
||||||
void drawBox();
|
void drawBox();
|
||||||
void drawIcon();
|
void drawItems();
|
||||||
|
|
||||||
bool isHovered() const;
|
bool isHovered() const;
|
||||||
bool isPressed() const;
|
bool isPressed() const;
|
||||||
|
|
|
@ -61,6 +61,7 @@ namespace ui
|
||||||
m_parent = nullptr;
|
m_parent = nullptr;
|
||||||
m_children.clear();
|
m_children.clear();
|
||||||
|
|
||||||
|
m_label = "";
|
||||||
m_main_box.reset();
|
m_main_box.reset();
|
||||||
|
|
||||||
m_events = 0;
|
m_events = 0;
|
||||||
|
@ -72,6 +73,8 @@ namespace ui
|
||||||
|
|
||||||
if (testShift(set_mask))
|
if (testShift(set_mask))
|
||||||
readChildren(is);
|
readChildren(is);
|
||||||
|
if (testShift(set_mask))
|
||||||
|
m_label = readStr16(is);
|
||||||
if (testShift(set_mask))
|
if (testShift(set_mask))
|
||||||
m_main_box.read(is);
|
m_main_box.read(is);
|
||||||
|
|
||||||
|
@ -79,7 +82,9 @@ namespace ui
|
||||||
for (Elem *elem : m_children) {
|
for (Elem *elem : m_children) {
|
||||||
content.push_back(&elem->getMain());
|
content.push_back(&elem->getMain());
|
||||||
}
|
}
|
||||||
|
|
||||||
m_main_box.setContent(std::move(content));
|
m_main_box.setContent(std::move(content));
|
||||||
|
m_main_box.setLabel(m_label);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Elem::isFocused() const
|
bool Elem::isFocused() const
|
||||||
|
|
|
@ -49,6 +49,8 @@ namespace ui
|
||||||
Elem *m_parent;
|
Elem *m_parent;
|
||||||
std::vector<Elem *> m_children;
|
std::vector<Elem *> m_children;
|
||||||
|
|
||||||
|
std::string m_label;
|
||||||
|
|
||||||
Box m_main_box;
|
Box m_main_box;
|
||||||
u64 m_hovered_box = Box::NO_ID; // Persistent
|
u64 m_hovered_box = Box::NO_ID; // Persistent
|
||||||
u64 m_pressed_box = Box::NO_ID; // Persistent
|
u64 m_pressed_box = Box::NO_ID; // Persistent
|
||||||
|
|
|
@ -8,6 +8,7 @@
|
||||||
#include "util/serialize.h"
|
#include "util/serialize.h"
|
||||||
|
|
||||||
#include <dimension2d.h>
|
#include <dimension2d.h>
|
||||||
|
#include <IGUIFont.h>
|
||||||
#include <ITexture.h>
|
#include <ITexture.h>
|
||||||
#include <rect.h>
|
#include <rect.h>
|
||||||
#include <vector2d.h>
|
#include <vector2d.h>
|
||||||
|
|
|
@ -43,6 +43,14 @@ namespace ui
|
||||||
return (IconPlace)place;
|
return (IconPlace)place;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static Align toAlign(u8 align)
|
||||||
|
{
|
||||||
|
if (align > (u8)Align::MAX) {
|
||||||
|
return Align::CENTER;
|
||||||
|
}
|
||||||
|
return (Align)align;
|
||||||
|
}
|
||||||
|
|
||||||
void Layout::reset()
|
void Layout::reset()
|
||||||
{
|
{
|
||||||
type = LayoutType::PLACE;
|
type = LayoutType::PLACE;
|
||||||
|
@ -134,6 +142,50 @@ namespace ui
|
||||||
frame_time = std::max(readU32(is), 1U);
|
frame_time = std::max(readU32(is), 1U);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Text::reset()
|
||||||
|
{
|
||||||
|
prepend = "";
|
||||||
|
append = "";
|
||||||
|
|
||||||
|
color = WHITE;
|
||||||
|
mark = BLANK;
|
||||||
|
size = 16;
|
||||||
|
|
||||||
|
mono = false;
|
||||||
|
italic = false;
|
||||||
|
bold = false;
|
||||||
|
|
||||||
|
align = Align::CENTER;
|
||||||
|
valign = Align::CENTER;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Text::read(std::istream &full_is)
|
||||||
|
{
|
||||||
|
auto is = newIs(readStr16(full_is));
|
||||||
|
u32 set_mask = readU32(is);
|
||||||
|
|
||||||
|
if (testShift(set_mask))
|
||||||
|
prepend = readStr16(is);
|
||||||
|
if (testShift(set_mask))
|
||||||
|
append = readStr16(is);
|
||||||
|
|
||||||
|
if (testShift(set_mask))
|
||||||
|
color = readARGB8(is);
|
||||||
|
if (testShift(set_mask))
|
||||||
|
mark = readARGB8(is);
|
||||||
|
if (testShift(set_mask))
|
||||||
|
size = std::clamp(readU32(is), 1U, 999U);
|
||||||
|
|
||||||
|
testShiftBool(set_mask, mono);
|
||||||
|
testShiftBool(set_mask, italic);
|
||||||
|
testShiftBool(set_mask, bold);
|
||||||
|
|
||||||
|
if (testShift(set_mask))
|
||||||
|
align = toAlign(readU8(is));
|
||||||
|
if (testShift(set_mask))
|
||||||
|
valign = toAlign(readU8(is));
|
||||||
|
}
|
||||||
|
|
||||||
void Style::reset()
|
void Style::reset()
|
||||||
{
|
{
|
||||||
layout.reset();
|
layout.reset();
|
||||||
|
@ -150,6 +202,8 @@ namespace ui
|
||||||
icon_place = IconPlace::CENTER;
|
icon_place = IconPlace::CENTER;
|
||||||
icon_gutter = 0.0f;
|
icon_gutter = 0.0f;
|
||||||
icon_overlap = false;
|
icon_overlap = false;
|
||||||
|
|
||||||
|
text.reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Style::read(std::istream &is)
|
void Style::read(std::istream &is)
|
||||||
|
@ -181,5 +235,8 @@ namespace ui
|
||||||
if (testShift(set_mask))
|
if (testShift(set_mask))
|
||||||
icon_gutter = readF32(is);
|
icon_gutter = readF32(is);
|
||||||
testShiftBool(set_mask, icon_overlap);
|
testShiftBool(set_mask, icon_overlap);
|
||||||
|
|
||||||
|
if (testShift(set_mask))
|
||||||
|
text.read(is);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -53,6 +53,16 @@ namespace ui
|
||||||
MAX = BOTTOM,
|
MAX = BOTTOM,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Serialized enum; do not change order of entries.
|
||||||
|
enum class Align : u8
|
||||||
|
{
|
||||||
|
START,
|
||||||
|
CENTER,
|
||||||
|
END,
|
||||||
|
|
||||||
|
MAX = END,
|
||||||
|
};
|
||||||
|
|
||||||
struct Layout
|
struct Layout
|
||||||
{
|
{
|
||||||
LayoutType type;
|
LayoutType type;
|
||||||
|
@ -101,6 +111,28 @@ namespace ui
|
||||||
void read(std::istream &is);
|
void read(std::istream &is);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct Text
|
||||||
|
{
|
||||||
|
std::string prepend;
|
||||||
|
std::string append;
|
||||||
|
|
||||||
|
video::SColor color;
|
||||||
|
video::SColor mark;
|
||||||
|
u32 size;
|
||||||
|
|
||||||
|
bool mono;
|
||||||
|
bool italic;
|
||||||
|
bool bold;
|
||||||
|
|
||||||
|
Align align;
|
||||||
|
Align valign;
|
||||||
|
|
||||||
|
Text() { reset(); }
|
||||||
|
|
||||||
|
void reset();
|
||||||
|
void read(std::istream &is);
|
||||||
|
};
|
||||||
|
|
||||||
struct Style
|
struct Style
|
||||||
{
|
{
|
||||||
Layout layout;
|
Layout layout;
|
||||||
|
@ -118,6 +150,8 @@ namespace ui
|
||||||
float icon_gutter;
|
float icon_gutter;
|
||||||
bool icon_overlap;
|
bool icon_overlap;
|
||||||
|
|
||||||
|
Text text;
|
||||||
|
|
||||||
Style() { reset(); }
|
Style() { reset(); }
|
||||||
|
|
||||||
void reset();
|
void reset();
|
||||||
|
|
|
@ -162,6 +162,41 @@ namespace ui
|
||||||
return PosF(x, y) / getScale();
|
return PosF(x, y) / getScale();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SizeF Window::getTextSize(gui::IGUIFont *font, std::wstring_view text)
|
||||||
|
{
|
||||||
|
// If we have an empty string, we want it to take up no space. IGUIFont
|
||||||
|
// measures the dimensions of an empty string as having the normal line
|
||||||
|
// height rather than no space.
|
||||||
|
if (text.empty()) {
|
||||||
|
return SizeF();
|
||||||
|
}
|
||||||
|
|
||||||
|
// IGUIFont measures the height of text with newlines incorrectly, so
|
||||||
|
// we have to measure each line in the string individually.
|
||||||
|
SizeF text_size = SizeF();
|
||||||
|
size_t start = 0;
|
||||||
|
|
||||||
|
while (start <= text.size()) {
|
||||||
|
// Get the line spanning from the start of this line to the next
|
||||||
|
// newline, or the end of the string if there are no more newlines.
|
||||||
|
size_t end = std::min(text.find(L'\n', start), text.size());
|
||||||
|
std::wstring line(text.substr(start, end - start));
|
||||||
|
|
||||||
|
// Get the dimensions of the line. Since fonts are already scaled,
|
||||||
|
// we have to reverse the scaling factor to get the right size.
|
||||||
|
SizeF line_size = SizeF(font->getDimension(
|
||||||
|
unescape_enriched(line).c_str())) / getScale();
|
||||||
|
|
||||||
|
text_size.W = std::max(text_size.W, line_size.W);
|
||||||
|
text_size.H += line_size.H;
|
||||||
|
|
||||||
|
// Move the start of the current line to after the end of this one.
|
||||||
|
start = end + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return text_size;
|
||||||
|
}
|
||||||
|
|
||||||
void Window::drawRect(RectF dst, RectF clip, video::SColor color)
|
void Window::drawRect(RectF dst, RectF clip, video::SColor color)
|
||||||
{
|
{
|
||||||
if (dst.intersectWith(clip).empty() || color.getAlpha() == 0) {
|
if (dst.intersectWith(clip).empty() || color.getAlpha() == 0) {
|
||||||
|
@ -189,6 +224,81 @@ namespace ui
|
||||||
src * DispF(getTextureSize(texture)), &scaled_clip, colors, true);
|
src * DispF(getTextureSize(texture)), &scaled_clip, colors, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Window::drawText(RectF dst, RectF clip, gui::IGUIFont *font,
|
||||||
|
std::wstring_view text, video::SColor color, video::SColor mark,
|
||||||
|
Align align, Align valign)
|
||||||
|
{
|
||||||
|
if (dst.intersectWith(clip).empty() || font == nullptr || text.empty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// We count the number of lines in the text to find the total height.
|
||||||
|
size_t num_lines = std::count(text.begin(), text.end(), L'\n') + 1;
|
||||||
|
|
||||||
|
// Get the height of a single line, and use this with the vertical
|
||||||
|
// alignment to find the vertical position of the first line.
|
||||||
|
float height = font->getDimension(L"").Height / getScale();
|
||||||
|
float top;
|
||||||
|
|
||||||
|
switch (valign) {
|
||||||
|
case Align::START:
|
||||||
|
top = dst.T;
|
||||||
|
break;
|
||||||
|
case Align::CENTER:
|
||||||
|
top = (dst.T + dst.B - (height * num_lines)) / 2.0f;
|
||||||
|
break;
|
||||||
|
case Align::END:
|
||||||
|
top = dst.B - (height * num_lines);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
core::recti scaled_clip = clip * getScale();
|
||||||
|
|
||||||
|
// Like getTextSize(), we loop over each line in the string.
|
||||||
|
size_t start = 0;
|
||||||
|
|
||||||
|
while (start <= text.size()) {
|
||||||
|
size_t end = std::min(text.find(L'\n', start), text.size());
|
||||||
|
std::wstring line(text.substr(start, end - start));
|
||||||
|
|
||||||
|
// Get the width of this line of text. Just like the height, we use
|
||||||
|
// the alignment to find the horizontal position for this line.
|
||||||
|
float width = font->getDimension(
|
||||||
|
unescape_enriched(line).c_str()).Width / getScale();
|
||||||
|
float left;
|
||||||
|
|
||||||
|
switch (align) {
|
||||||
|
case Align::START:
|
||||||
|
left = dst.L;
|
||||||
|
break;
|
||||||
|
case Align::CENTER:
|
||||||
|
left = (dst.L + dst.R - width) / 2.0f;
|
||||||
|
break;
|
||||||
|
case Align::END:
|
||||||
|
left = dst.R - width;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// This gives us the destination rect for this line of the text,
|
||||||
|
// which we scale appropriately.
|
||||||
|
RectF line_rect = RectF(PosF(left, top), SizeF(width, height)) * getScale();
|
||||||
|
|
||||||
|
// If we have a highlight color for the text, draw the highlight
|
||||||
|
// before we draw the line.
|
||||||
|
if (mark.getAlpha() != 0) {
|
||||||
|
RenderingEngine::get_video_driver()->draw2DRectangle(
|
||||||
|
mark, line_rect, &scaled_clip);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Then draw the text itself using the provided font.
|
||||||
|
font->draw(line.c_str(), line_rect, color, false, false, &scaled_clip);
|
||||||
|
|
||||||
|
// Finally, advance to the next line.
|
||||||
|
top += height;
|
||||||
|
start = end + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void Window::drawAll()
|
void Window::drawAll()
|
||||||
{
|
{
|
||||||
Box &backdrop = m_root_elem->getBackdrop();
|
Box &backdrop = m_root_elem->getBackdrop();
|
||||||
|
|
|
@ -98,9 +98,14 @@ namespace ui
|
||||||
SizeF getScreenSize() const;
|
SizeF getScreenSize() const;
|
||||||
PosF getPointerPos() const;
|
PosF getPointerPos() const;
|
||||||
|
|
||||||
|
SizeF getTextSize(gui::IGUIFont *font, std::wstring_view text);
|
||||||
|
|
||||||
void drawRect(RectF dst, RectF clip, video::SColor color);
|
void drawRect(RectF dst, RectF clip, video::SColor color);
|
||||||
void drawTexture(RectF dst, RectF clip, video::ITexture *texture,
|
void drawTexture(RectF dst, RectF clip, video::ITexture *texture,
|
||||||
RectF src = RectF(0.0f, 0.0f, 1.0f, 1.0f), video::SColor tint = WHITE);
|
RectF src = RectF(0.0f, 0.0f, 1.0f, 1.0f), video::SColor tint = WHITE);
|
||||||
|
void drawText(RectF dst, RectF clip, gui::IGUIFont *font, std::wstring_view text,
|
||||||
|
video::SColor color = WHITE, video::SColor mark = BLANK,
|
||||||
|
Align align = Align::START, Align valign = Align::START);
|
||||||
|
|
||||||
void drawAll();
|
void drawAll();
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue