931 lines
25 KiB
C++
931 lines
25 KiB
C++
/*
|
|
* A Label Control Implementation
|
|
* Nana C++ Library(http://www.nanapro.org)
|
|
* Copyright(C) 2003-2018 Jinhao(cnjinhao@hotmail.com)
|
|
*
|
|
* Distributed under the Boost Software License, Version 1.0.
|
|
* (See accompanying file LICENSE_1_0.txt or copy at
|
|
* http://www.boost.org/LICENSE_1_0.txt)
|
|
*
|
|
* @file: source/gui/widgets/label.cpp
|
|
* @author: Jinhao
|
|
* @contributors: Ariel Vina-Rodriguez
|
|
*/
|
|
|
|
#include <nana/gui/widgets/label.hpp>
|
|
#include <nana/gui/widgets/skeletons/text_token_stream.hpp>
|
|
#include <nana/gui/detail/widget_content_measurer_interface.hpp>
|
|
#include <nana/unicode_bidi.hpp>
|
|
#include <nana/system/platform.hpp>
|
|
#include <stdexcept>
|
|
#include <sstream>
|
|
|
|
|
|
namespace nana
|
|
{
|
|
namespace drawerbase
|
|
{
|
|
namespace label
|
|
{
|
|
class renderer
|
|
{
|
|
//Iterator of content element in a line.
|
|
using content_element_iterator = widgets::skeletons::dstream::linecontainer::const_iterator; //subsitute for member type iterator
|
|
|
|
struct visual_line //subsitute of pixel_tag
|
|
{
|
|
struct element
|
|
{
|
|
content_element_iterator content_element;
|
|
std::pair<std::size_t, std::size_t> range; //A part of text in a text element. first: text begin, second: text length
|
|
|
|
element(const content_element_iterator& iterator, std::size_t range_begin, std::size_t range_end):
|
|
content_element(iterator),
|
|
range(range_begin, range_end)
|
|
{
|
|
}
|
|
};
|
|
|
|
int x_base; //The x position where this line starts.
|
|
|
|
std::size_t extent_height_px;
|
|
std::size_t baseline; //The baseline for rendering text.
|
|
std::vector<element> elements; //description of text element in this rendering line.
|
|
};
|
|
|
|
//this is a helper variable, it just keeps the status while drawing.
|
|
struct render_status
|
|
{
|
|
unsigned allowed_width;
|
|
align text_align;
|
|
align_v text_align_v;
|
|
|
|
nana::point pos;
|
|
std::vector<visual_line> vslines; //The lines description of a line of text. substitute of member pixels.
|
|
std::size_t index; //indicates the current rendering visual line.
|
|
};
|
|
|
|
struct traceable
|
|
{
|
|
nana::rectangle r;
|
|
std::wstring target;
|
|
std::wstring url;
|
|
};
|
|
|
|
public:
|
|
typedef nana::paint::graphics& graph_reference;
|
|
typedef widgets::skeletons::dstream dstream;
|
|
typedef widgets::skeletons::fblock fblock;
|
|
typedef widgets::skeletons::data data;
|
|
|
|
void parse(const std::wstring& s)
|
|
{
|
|
dstream_.parse(s, format_enabled_);
|
|
}
|
|
|
|
bool format(bool fm) noexcept
|
|
{
|
|
if (fm == format_enabled_)
|
|
return false;
|
|
|
|
format_enabled_ = fm;
|
|
return true;
|
|
}
|
|
|
|
void render(graph_reference graph, const ::nana::color& fgcolor, align th, align_v tv)
|
|
{
|
|
traceable_.clear();
|
|
|
|
render_status rs;
|
|
|
|
rs.allowed_width = graph.size().width;
|
|
rs.text_align = th;
|
|
rs.text_align_v = tv;
|
|
|
|
::nana::size extent_size;
|
|
|
|
//All visual lines data of whole text.
|
|
auto content_lines = _m_measure_extent_size(graph, th, tv, true, graph.size().width, extent_size);
|
|
|
|
if ((tv != align_v::top) && extent_size.height < graph.height())
|
|
{
|
|
rs.pos.y = static_cast<int>(graph.height() - extent_size.height);
|
|
|
|
if (align_v::center == tv)
|
|
rs.pos.y >>= 1;
|
|
}
|
|
else
|
|
rs.pos.y = 0;
|
|
|
|
auto pre_font = graph.typeface(); //used for restoring the font
|
|
_m_set_default(pre_font, fgcolor);
|
|
|
|
|
|
for (auto & line : content_lines)
|
|
{
|
|
rs.index = 0;
|
|
rs.vslines.swap(line);
|
|
rs.pos.x = rs.vslines.front().x_base;
|
|
|
|
if (!_m_foreach_visual_line(graph, rs))
|
|
break;
|
|
|
|
//Now the y-position of rs has been modified to next line.
|
|
}
|
|
|
|
if (transient_.current_font != pre_font)
|
|
{
|
|
graph.typeface(pre_font);
|
|
transient_.current_font.release();
|
|
transient_.current_fblock = nullptr;
|
|
}
|
|
}
|
|
|
|
bool find(const point& mouse_pos, std::wstring& target, std::wstring& url) const
|
|
{
|
|
for (auto & t : traceable_)
|
|
{
|
|
if(t.r.is_hit(mouse_pos))
|
|
{
|
|
target = t.target;
|
|
url = t.url;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
::nana::size measure(graph_reference graph, unsigned limited, align th, align_v tv)
|
|
{
|
|
::nana::size extent_size;
|
|
_m_measure_extent_size(graph, th, tv, false, limited, extent_size);
|
|
|
|
return extent_size;
|
|
}
|
|
private:
|
|
//Manage the fblock for a specified rectangle if it is a traceable fblock.
|
|
void _m_insert_if_traceable(int x, int y, const nana::size& sz, widgets::skeletons::fblock* fbp)
|
|
{
|
|
if(fbp->target.size() || fbp->url.size())
|
|
{
|
|
#ifdef _nana_std_has_emplace_return_type
|
|
auto & tr = traceable_.emplace_back();
|
|
#else
|
|
traceable_.emplace_back();
|
|
auto & tr = traceable_.back();
|
|
#endif
|
|
tr.r.x = x;
|
|
tr.r.y = y;
|
|
tr.r.dimension(sz);
|
|
tr.target = fbp->target;
|
|
tr.url = fbp->url;
|
|
}
|
|
}
|
|
|
|
void _m_set_default(const ::nana::paint::font& ft, const ::nana::color& fgcolor)
|
|
{
|
|
def_.font_name = ft.name();
|
|
def_.font_size = ft.size();
|
|
def_.font_bold = ft.bold();
|
|
def_.fgcolor = fgcolor;
|
|
|
|
transient_.current_font = ft;
|
|
transient_.current_fblock = nullptr;
|
|
}
|
|
|
|
const ::nana::color& _m_fgcolor(nana::widgets::skeletons::fblock* fp) noexcept
|
|
{
|
|
while(fp->fgcolor.invisible())
|
|
{
|
|
fp = fp->parent;
|
|
if(nullptr == fp)
|
|
return def_.fgcolor;
|
|
}
|
|
return fp->fgcolor;
|
|
}
|
|
|
|
double _m_font_size(nana::widgets::skeletons::fblock* fp) noexcept
|
|
{
|
|
while(fp->font_size < 0)
|
|
{
|
|
fp = fp->parent;
|
|
if(nullptr == fp)
|
|
return def_.font_size;
|
|
}
|
|
return fp->font_size;
|
|
}
|
|
|
|
bool _m_bold(nana::widgets::skeletons::fblock* fp) noexcept
|
|
{
|
|
while(fp->bold_empty)
|
|
{
|
|
fp = fp->parent;
|
|
if(nullptr == fp)
|
|
return def_.font_bold;
|
|
}
|
|
return fp->bold;
|
|
}
|
|
|
|
const std::string& _m_fontname(nana::widgets::skeletons::fblock* fp) noexcept
|
|
{
|
|
while(fp->font.empty())
|
|
{
|
|
fp = fp->parent;
|
|
if(nullptr == fp)
|
|
return def_.font_name;
|
|
}
|
|
return fp->font;
|
|
}
|
|
|
|
void _m_change_font(graph_reference graph, nana::widgets::skeletons::fblock* fp)
|
|
{
|
|
if (fp != transient_.current_fblock)
|
|
{
|
|
auto& name = _m_fontname(fp);
|
|
auto fontsize = _m_font_size(fp);
|
|
bool bold = _m_bold(fp);
|
|
|
|
if((fontsize != transient_.current_font.size()) || bold != transient_.current_font.bold() || name != transient_.current_font.name())
|
|
{
|
|
paint::font::font_style fs;
|
|
fs.weight = (bold ? 800 : 400);
|
|
transient_.current_font = paint::font{ name, fontsize, fs };
|
|
graph.typeface(transient_.current_font);
|
|
}
|
|
transient_.current_fblock = fp;
|
|
}
|
|
}
|
|
|
|
void _m_prepare_x(const render_status& rs, visual_line & vsline, unsigned w) noexcept
|
|
{
|
|
switch (rs.text_align)
|
|
{
|
|
case align::left:
|
|
vsline.x_base = 0;
|
|
break;
|
|
case align::center:
|
|
vsline.x_base = (static_cast<int>(rs.allowed_width - w) >> 1);
|
|
break;
|
|
case align::right:
|
|
vsline.x_base = static_cast<int>(rs.allowed_width - w);
|
|
break;
|
|
}
|
|
}
|
|
|
|
std::deque<std::vector<visual_line>> _m_measure_extent_size(graph_reference graph, nana::align text_align, nana::align_v text_align_v, bool only_screen, unsigned allowed_width_px, nana::size & extent_size)
|
|
{
|
|
auto pre_font = graph.typeface(); //used for restoring the font
|
|
|
|
unsigned text_ascent, text_descent, text_ileading;
|
|
graph.text_metrics(text_ascent, text_descent, text_ileading);
|
|
|
|
auto const def_line_pixels = text_ascent + text_descent;
|
|
|
|
_m_set_default(pre_font, colors::black);
|
|
|
|
render_status rs;
|
|
|
|
rs.allowed_width = allowed_width_px;
|
|
rs.text_align = text_align;
|
|
rs.text_align_v = text_align_v;
|
|
|
|
//All visual lines data of whole text.
|
|
std::deque<std::vector<visual_line>> content_lines;
|
|
|
|
extent_size.width = extent_size.height = 0;
|
|
|
|
for (auto & line : dstream_)
|
|
{
|
|
auto width_px = _m_prepare_visual_lines(graph, line, def_line_pixels, rs);
|
|
|
|
if (width_px > extent_size.width)
|
|
extent_size.width = width_px;
|
|
|
|
for (auto & vsline : rs.vslines)
|
|
extent_size.height += static_cast<size::value_type>(vsline.extent_height_px);
|
|
|
|
content_lines.emplace_back(std::move(rs.vslines));
|
|
|
|
if (only_screen && (extent_size.height >= graph.height()))
|
|
break;
|
|
}
|
|
|
|
//The width is not restricted if the allowed_width_px is zero.
|
|
if (allowed_width_px && (allowed_width_px < extent_size.width))
|
|
extent_size.width = allowed_width_px;
|
|
|
|
if (transient_.current_font != pre_font)
|
|
{
|
|
graph.typeface(pre_font);
|
|
transient_.current_font.release();
|
|
transient_.current_fblock = nullptr;
|
|
}
|
|
|
|
return content_lines;
|
|
}
|
|
|
|
/**
|
|
* prepare data for rendering a line of text.
|
|
*/
|
|
unsigned _m_prepare_visual_lines(graph_reference graph, dstream::linecontainer& line, unsigned def_line_px, render_status& rs)
|
|
{
|
|
if (line.empty())
|
|
{
|
|
//Insert an empty visual line for empty content.
|
|
#ifdef _nana_std_has_emplace_return_type
|
|
auto & vsline = rs.vslines.emplace_back();
|
|
#else
|
|
rs.vslines.emplace_back();
|
|
auto & vsline = rs.vslines.back();
|
|
#endif
|
|
vsline.baseline = 0;
|
|
vsline.extent_height_px = def_line_px;
|
|
vsline.x_base = 0;
|
|
|
|
return 0;
|
|
}
|
|
|
|
unsigned abs_text_px = 0;
|
|
unsigned max_ascent = 0;
|
|
unsigned max_descent = 0;
|
|
unsigned max_content_height = 0;
|
|
|
|
int text_pos = 0;
|
|
|
|
std::vector<visual_line::element> vsline_elements;
|
|
|
|
for (auto i = line.cbegin(); i != line.cend(); ++i)
|
|
{
|
|
auto const data = i->data_ptr;
|
|
auto const fblock = i->fblock_ptr;
|
|
|
|
_m_change_font(graph, fblock);
|
|
data->measure(graph);
|
|
|
|
abs_text_px += data->size().width;
|
|
|
|
unsigned ascent = 0;
|
|
unsigned descent = 0;
|
|
|
|
|
|
auto extent_size = data->size();
|
|
|
|
//Check if the content is displayed in current line.
|
|
if ((0 == rs.allowed_width) || (text_pos + extent_size.width <= rs.allowed_width))
|
|
{
|
|
text_pos += static_cast<int>(extent_size.width);
|
|
|
|
//Adjust height of extent_size for special text alignement.
|
|
if (fblock::aligns::baseline == fblock->text_align)
|
|
{
|
|
ascent = static_cast<unsigned>(data->ascent());
|
|
descent = static_cast<unsigned>(extent_size.height - ascent);
|
|
|
|
if (max_descent < descent)
|
|
max_descent = descent;
|
|
|
|
if ((false == data->is_text()) && (extent_size.height < max_ascent + max_descent))
|
|
extent_size.height = max_ascent + max_descent;
|
|
}
|
|
|
|
if (max_ascent < ascent) max_ascent = ascent;
|
|
if (max_descent < descent) max_descent = descent;
|
|
if (max_content_height < extent_size.height) max_content_height = extent_size.height;
|
|
vsline_elements.emplace_back(i, 0, data->text().size());
|
|
|
|
continue;
|
|
}
|
|
|
|
//make a visual line for existing vsline elements
|
|
if (text_pos)
|
|
{
|
|
#ifdef _nana_std_has_emplace_return_type
|
|
auto & vsline = rs.vslines.emplace_back();
|
|
#else
|
|
rs.vslines.emplace_back();
|
|
auto & vsline = rs.vslines.back();
|
|
#endif
|
|
_m_prepare_x(rs, vsline, static_cast<unsigned>(text_pos));
|
|
|
|
if (max_ascent + max_descent > max_content_height)
|
|
max_content_height = max_descent + max_ascent;
|
|
else
|
|
max_ascent = max_content_height - max_descent;
|
|
|
|
vsline.extent_height_px = max_content_height;
|
|
vsline.baseline = max_ascent;
|
|
vsline.elements.swap(vsline_elements);
|
|
}
|
|
|
|
text_pos = 0;
|
|
max_content_height = max_ascent = max_descent = 0;
|
|
//Adjust height of extent_size for special text alignement.
|
|
if (fblock::aligns::baseline == fblock->text_align)
|
|
{
|
|
ascent = static_cast<unsigned>(data->ascent());
|
|
descent = static_cast<unsigned>(extent_size.height - ascent);
|
|
|
|
if (max_descent < descent)
|
|
max_descent = descent;
|
|
|
|
if ((false == data->is_text()) && (extent_size.height < max_ascent + max_descent))
|
|
extent_size.height = max_ascent + max_descent;
|
|
}
|
|
|
|
if (max_ascent < ascent) max_ascent = ascent;
|
|
if (max_descent < descent) max_descent = descent;
|
|
if (max_content_height < extent_size.height) max_content_height = extent_size.height;
|
|
|
|
if (data->is_text())
|
|
{
|
|
//Split a text into multiple lines
|
|
std::size_t text_begin = 0;
|
|
while (text_begin < data->text().size())
|
|
{
|
|
unsigned sub_text_px = 0;
|
|
auto sub_text_len = _m_fit_text(graph, data->text().substr(text_begin), rs.allowed_width, sub_text_px);
|
|
|
|
//At least one character must be displayed no matter whether the width is enough or not.
|
|
if (0 == sub_text_len)
|
|
sub_text_len = 1;
|
|
|
|
if (text_begin + sub_text_len < data->text().size())
|
|
{
|
|
//make a new visual line
|
|
#ifdef _nana_std_has_emplace_return_type
|
|
auto & vsline = rs.vslines.emplace_back();
|
|
#else
|
|
rs.vslines.emplace_back();
|
|
auto & vsline = rs.vslines.back();
|
|
#endif
|
|
_m_prepare_x(rs, vsline, sub_text_px);
|
|
|
|
vsline.extent_height_px = max_content_height;
|
|
vsline.baseline = max_ascent;
|
|
vsline.elements.emplace_back(i, text_begin, sub_text_len);
|
|
}
|
|
else
|
|
{
|
|
//the last part, write it to vsline_elements to keep the status for next line element(next i)
|
|
vsline_elements.emplace_back(i, text_begin, sub_text_len);
|
|
|
|
text_pos = sub_text_px;
|
|
}
|
|
|
|
text_begin += sub_text_len;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//the last part, write it to vsline_elements to keep the status for next line element(next i)
|
|
vsline_elements.emplace_back(i, 0, 0);
|
|
|
|
text_pos = static_cast<int>(i->data_ptr->size().width);
|
|
}
|
|
}
|
|
|
|
if (!vsline_elements.empty())
|
|
{
|
|
#ifdef _nana_std_has_emplace_return_type
|
|
auto & vsline = rs.vslines.emplace_back();
|
|
#else
|
|
rs.vslines.emplace_back();
|
|
auto & vsline = rs.vslines.back();
|
|
#endif
|
|
_m_prepare_x(rs, vsline, static_cast<unsigned>(text_pos));
|
|
|
|
if (max_ascent + max_descent > max_content_height)
|
|
max_content_height = max_descent + max_ascent;
|
|
else
|
|
max_ascent = max_content_height - max_descent;
|
|
|
|
vsline.extent_height_px = max_content_height;
|
|
vsline.baseline = max_ascent;
|
|
vsline.elements.swap(vsline_elements);
|
|
}
|
|
|
|
return abs_text_px;
|
|
}
|
|
|
|
//Get the length of characters in a text whose length in pixels doesn't beyond the limited width.
|
|
static unsigned _m_fit_text(graph_reference graph, const std::wstring& text, unsigned limited_width_px, unsigned& text_px) noexcept
|
|
{
|
|
#ifdef _nana_std_has_string_view
|
|
auto pxbuf = graph.glyph_pixels(text);
|
|
#else
|
|
std::unique_ptr<unsigned[]> pxbuf(new unsigned[text.size()]);
|
|
graph.glyph_pixels(text.c_str(), text.size(), pxbuf.get());
|
|
#endif
|
|
|
|
text_px = 0;
|
|
for (unsigned i = 0; i < text.size(); ++i)
|
|
{
|
|
if (text_px + pxbuf[i] > limited_width_px)
|
|
return i;
|
|
|
|
text_px += pxbuf[i];
|
|
}
|
|
return static_cast<unsigned>(text.size());
|
|
}
|
|
|
|
bool _m_foreach_visual_line(graph_reference graph, render_status& rs)
|
|
{
|
|
auto const bottom = static_cast<int>(graph.height()) - 1;
|
|
|
|
for (auto & vsline : rs.vslines)
|
|
{
|
|
rs.pos.x = vsline.x_base;
|
|
for (auto& content_elm : vsline.elements)
|
|
{
|
|
_m_draw_vsline_element(graph, content_elm, rs);
|
|
}
|
|
|
|
++rs.index; //next line index
|
|
rs.pos.y += static_cast<int>(vsline.extent_height_px);
|
|
|
|
if (rs.pos.y > bottom)
|
|
return false;
|
|
}
|
|
|
|
return (rs.pos.y <= bottom);
|
|
}
|
|
|
|
static int _m_vsline_element_top(const visual_line& vsline, fblock* fblock_ptr, const data* data_ptr) noexcept
|
|
{
|
|
switch (fblock_ptr->text_align)
|
|
{
|
|
case fblock::aligns::center:
|
|
return static_cast<int>(vsline.extent_height_px - data_ptr->size().height) / 2;
|
|
case fblock::aligns::bottom:
|
|
return static_cast<int>(vsline.extent_height_px - data_ptr->size().height);
|
|
case fblock::aligns::baseline:
|
|
return static_cast<int>(vsline.baseline - (data_ptr->is_text() ? data_ptr->ascent() : data_ptr->size().height));
|
|
default: break;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
void _m_draw_vsline_element(graph_reference graph, const visual_line::element& vsline_elm, render_status& rs)
|
|
{
|
|
auto data = vsline_elm.content_element->data_ptr;
|
|
auto fblock = vsline_elm.content_element->fblock_ptr;
|
|
|
|
if (data->is_text())
|
|
{
|
|
auto const reordered = unicode_reorder(data->text().c_str() + vsline_elm.range.first, vsline_elm.range.second);
|
|
|
|
_m_change_font(graph, fblock);
|
|
for (auto & bidi : reordered)
|
|
{
|
|
auto extent_size = data->size();
|
|
#ifdef _nana_std_has_string_view
|
|
std::wstring_view text_sv{ bidi.begin, static_cast<std::size_t>(bidi.end - bidi.begin) };
|
|
if (data->text().size() != text_sv.size())
|
|
extent_size = graph.text_extent_size(text_sv);
|
|
|
|
const int y = rs.pos.y + _m_vsline_element_top(rs.vslines[rs.index], fblock, data);
|
|
graph.string({ rs.pos.x, y }, text_sv, _m_fgcolor(fblock));
|
|
#else
|
|
std::wstring text{ bidi.begin, static_cast<std::size_t>(bidi.end - bidi.begin) };
|
|
if (data->text().size() != text.size())
|
|
extent_size = graph.text_extent_size(text);
|
|
|
|
const int y = rs.pos.y + _m_vsline_element_top(rs.vslines[rs.index], fblock, data);
|
|
graph.string({ rs.pos.x, y }, text, _m_fgcolor(fblock));
|
|
#endif
|
|
_m_insert_if_traceable(rs.pos.x, y, extent_size, fblock);
|
|
rs.pos.x += static_cast<int>(extent_size.width);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
int y = rs.pos.y + _m_vsline_element_top(rs.vslines[rs.index], fblock, data);
|
|
|
|
data->nontext_render(graph, rs.pos.x, y);
|
|
_m_insert_if_traceable(rs.pos.x, y, data->size(), fblock);
|
|
rs.pos.x += static_cast<int>(data->size().width);
|
|
}
|
|
}
|
|
private:
|
|
dstream dstream_;
|
|
bool format_enabled_ = false;
|
|
|
|
::std::deque<traceable> traceable_;
|
|
|
|
struct transient
|
|
{
|
|
widgets::skeletons::fblock * current_fblock{ nullptr };
|
|
paint::font current_font;
|
|
}transient_;
|
|
|
|
struct def_font_tag
|
|
{
|
|
::std::string font_name;
|
|
double font_size;
|
|
bool font_bold;
|
|
::nana::color fgcolor;
|
|
}def_;
|
|
};
|
|
|
|
//class trigger
|
|
//@brief: Draw the label
|
|
struct trigger::implement
|
|
{
|
|
class measurer;
|
|
|
|
widget * wd{nullptr};
|
|
paint::graphics * graph{nullptr};
|
|
std::unique_ptr<measurer> msr_ptr{ nullptr };
|
|
|
|
align text_align{align::left};
|
|
align_v text_align_v{align_v::top};
|
|
|
|
class renderer renderer;
|
|
|
|
std::wstring target; //It indicates which target is tracing.
|
|
std::wstring url;
|
|
|
|
window for_associated_wd{ nullptr };
|
|
|
|
void add_listener(std::function<void(command, const std::string&)>&& fn)
|
|
{
|
|
listener_.emplace_back(std::move(fn));
|
|
}
|
|
|
|
void call_listener(command cmd, const std::wstring& tar)
|
|
{
|
|
auto str = to_utf8(tar);
|
|
for (auto & fn : listener_)
|
|
fn(cmd, str);
|
|
}
|
|
private:
|
|
std::vector<std::function<void(command, const std::string&)>> listener_;
|
|
};
|
|
|
|
class trigger::implement::measurer
|
|
: public dev::widget_content_measurer_interface
|
|
{
|
|
public:
|
|
measurer(implement* impl)
|
|
: impl_{ impl }
|
|
{}
|
|
|
|
std::optional<size> measure(graph_reference graph, unsigned limit_pixels, bool limit_width) const override
|
|
{
|
|
//Label now doesn't support to measure content with a specified height.
|
|
if (graph && ((0 == limit_pixels) || limit_width))
|
|
{
|
|
return impl_->renderer.measure(graph, limit_pixels, impl_->text_align, impl_->text_align_v);
|
|
}
|
|
return{};
|
|
}
|
|
|
|
size extension() const override
|
|
{
|
|
return{ 2, 2 };
|
|
}
|
|
private:
|
|
implement * const impl_;
|
|
};
|
|
|
|
trigger::trigger()
|
|
:impl_(new implement)
|
|
{
|
|
impl_->msr_ptr.reset(new trigger::implement::measurer{impl_});
|
|
}
|
|
|
|
trigger::~trigger()
|
|
{
|
|
delete impl_;
|
|
}
|
|
|
|
trigger::implement * trigger::impl() const
|
|
{
|
|
return impl_;
|
|
}
|
|
|
|
void trigger::attached(widget_reference widget, graph_reference graph)
|
|
{
|
|
impl_->graph = &graph;
|
|
impl_->wd = &widget;
|
|
API::dev::set_measurer(widget, impl_->msr_ptr.get());
|
|
}
|
|
|
|
void trigger::mouse_move(graph_reference, const arg_mouse& arg)
|
|
{
|
|
std::wstring target, url;
|
|
|
|
if(impl_->renderer.find(arg.pos, target, url))
|
|
{
|
|
int cur_state = 0;
|
|
if(target != impl_->target)
|
|
{
|
|
if(impl_->target.size())
|
|
{
|
|
impl_->call_listener(command::leave, impl_->target);
|
|
cur_state = 1; //Set arrow
|
|
}
|
|
|
|
impl_->target = target;
|
|
|
|
if(target.size())
|
|
{
|
|
impl_->call_listener(command::enter, impl_->target);
|
|
cur_state = 2; //Set hand
|
|
}
|
|
}
|
|
if (url != impl_->url)
|
|
{
|
|
if (impl_->url.size())
|
|
cur_state = 1; //Set arrow
|
|
|
|
impl_->url = url;
|
|
|
|
if (url.size())
|
|
cur_state = 2; //Set hand
|
|
}
|
|
|
|
if (cur_state)
|
|
impl_->wd->cursor(1 == cur_state ? cursor::arrow : cursor::hand);
|
|
}
|
|
else
|
|
{
|
|
bool restore = false;
|
|
if (impl_->target.size())
|
|
{
|
|
impl_->call_listener(command::leave, impl_->target);
|
|
impl_->target.clear();
|
|
restore = true;
|
|
}
|
|
|
|
if (impl_->url.size())
|
|
{
|
|
impl_->url.clear();
|
|
restore = true;
|
|
}
|
|
|
|
if(restore)
|
|
impl_->wd->cursor(cursor::arrow);
|
|
}
|
|
}
|
|
|
|
void trigger::mouse_leave(graph_reference, const arg_mouse&)
|
|
{
|
|
if(impl_->target.size())
|
|
{
|
|
impl_->call_listener(command::leave, impl_->target);
|
|
impl_->target.clear();
|
|
impl_->wd->cursor(cursor::arrow);
|
|
}
|
|
}
|
|
|
|
void trigger::click(graph_reference, const arg_click&)
|
|
{
|
|
//make a copy, because the listener may popup a window, and then
|
|
//user moves the mouse. it will reset the url when the mouse is moving out from the element.
|
|
auto url = impl_->url;
|
|
|
|
if(impl_->target.size())
|
|
impl_->call_listener(command::click, impl_->target);
|
|
|
|
system::open_url(to_utf8(url));
|
|
|
|
API::focus_window(impl_->for_associated_wd);
|
|
}
|
|
|
|
void trigger::refresh(graph_reference graph)
|
|
{
|
|
if(nullptr == impl_->wd) return;
|
|
|
|
window wd = impl_->wd->handle();
|
|
if (!API::dev::copy_transparent_background(wd, graph))
|
|
graph.rectangle(true, API::bgcolor(wd));
|
|
|
|
impl_->renderer.render(graph, API::fgcolor(wd), impl_->text_align, impl_->text_align_v);
|
|
}
|
|
|
|
//end class label_drawer
|
|
}//end namespace label
|
|
}//end namespace drawerbase
|
|
|
|
|
|
//
|
|
//class label
|
|
label::label(){}
|
|
|
|
label::label(window wd, bool visible)
|
|
{
|
|
create(wd, rectangle(), visible);
|
|
bgcolor(API::bgcolor(wd));
|
|
}
|
|
|
|
label::label(window wd, const std::string& text, bool visible)
|
|
{
|
|
throw_not_utf8(text);
|
|
create(wd, rectangle(), visible);
|
|
bgcolor(API::bgcolor(wd));
|
|
caption(text);
|
|
}
|
|
|
|
label::label(window wd, const rectangle& r, bool visible)
|
|
{
|
|
create(wd, r, visible);
|
|
bgcolor(API::bgcolor(wd));
|
|
}
|
|
|
|
label& label::transparent(bool enabled)
|
|
{
|
|
if(enabled)
|
|
API::effects_bground(*this, effects::bground_transparent(0), 0.0);
|
|
else
|
|
API::effects_bground_remove(*this);
|
|
return *this;
|
|
}
|
|
|
|
bool label::transparent() const noexcept
|
|
{
|
|
return API::is_transparent_background(*this);
|
|
}
|
|
|
|
label& label::format(bool f)
|
|
{
|
|
auto impl = get_drawer_trigger().impl();
|
|
|
|
if(impl->renderer.format(f))
|
|
{
|
|
window wd = *this;
|
|
impl->renderer.parse(::nana::to_wstring(API::dev::window_caption(wd)));
|
|
API::refresh_window(wd);
|
|
}
|
|
return *this;
|
|
}
|
|
|
|
label& label::add_format_listener(std::function<void(command, const std::string&)> f)
|
|
{
|
|
get_drawer_trigger().impl()->add_listener(std::move(f));
|
|
return *this;
|
|
}
|
|
|
|
label& label::click_for(window associated_window) noexcept
|
|
{
|
|
get_drawer_trigger().impl()->for_associated_wd = associated_window;
|
|
return *this;
|
|
}
|
|
|
|
nana::size label::measure(unsigned limited) const
|
|
{
|
|
if(empty())
|
|
return nana::size();
|
|
|
|
auto impl = get_drawer_trigger().impl();
|
|
|
|
//First Check the graph of label
|
|
//Then take a substitute for graph when the graph of label is zero-sized.
|
|
nana::paint::graphics * graph_ptr = impl->graph;
|
|
nana::paint::graphics substitute;
|
|
if(graph_ptr->empty())
|
|
{
|
|
graph_ptr = &substitute;
|
|
substitute.make({ 10, 10 });
|
|
substitute.typeface(this->typeface());
|
|
}
|
|
|
|
return impl->renderer.measure(*graph_ptr, limited, impl->text_align, impl->text_align_v);
|
|
}
|
|
|
|
::nana::size label::measure(paint::graphics& graph, const ::std::string& str, unsigned allowed_width_in_pixel, bool format_enabled, align h_align, align_v v_align)
|
|
{
|
|
throw_not_utf8(str);
|
|
drawerbase::label::renderer rd;
|
|
rd.format(format_enabled);
|
|
rd.parse(to_wstring(str));
|
|
return rd.measure(graph, allowed_width_in_pixel, h_align, v_align);
|
|
}
|
|
|
|
label& label::text_align(align th, align_v tv)
|
|
{
|
|
internal_scope_guard lock;
|
|
auto impl = get_drawer_trigger().impl();
|
|
|
|
if (th != impl->text_align || tv != impl->text_align_v)
|
|
{
|
|
impl->text_align = th;
|
|
impl->text_align_v = tv;
|
|
API::refresh_window(*this);
|
|
}
|
|
|
|
return *this;
|
|
}
|
|
|
|
void label::_m_caption(native_string_type&& str)
|
|
{
|
|
internal_scope_guard lock;
|
|
window wd = *this;
|
|
get_drawer_trigger().impl()->renderer.parse(to_wstring(str));
|
|
API::dev::window_caption(wd, std::move(str));
|
|
API::refresh_window(wd);
|
|
}
|
|
//end class label
|
|
}//end namespace nana
|