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 alignment.
 | 
						|
							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 alignment.
 | 
						|
						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
 |