nana/source/gui/widgets/tabbar.cpp
2017-12-19 03:30:38 +08:00

1730 lines
42 KiB
C++

/*
* A Tabbar Implementation
* Copyright(C) 2003-2017 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: nana/gui/widgets/tabbar.hpp
*/
#include <nana/gui/widgets/tabbar.hpp>
#include <nana/gui/widgets/menu.hpp>
#include <nana/paint/text_renderer.hpp>
#include <nana/gui/element.hpp>
#include <stdexcept>
#include <list>
namespace nana
{
namespace drawerbase
{
namespace tabbar
{
using native_string_type = ::nana::detail::native_string_type;
struct item_t
{
window relative{nullptr};
paint::image img;
native_string_type text;
any value;
::nana::color bgcolor;
::nana::color fgcolor;
item_t() = default;
item_t(native_string_type&& text, any && value)
: text(std::move(text)), value(std::move(value))
{}
};
class def_renderer
: public item_renderer
{
private:
virtual void background(graph_reference graph, const nana::rectangle&, const ::nana::color& bgcolor)
{
if(bgcolor_ != bgcolor)
{
bgcolor_ = bgcolor;
dark_bgcolor_ = bgcolor.blend(colors::black, 0.1);
blcolor_ = bgcolor.blend(colors::black, 0.5);
ilcolor_ = bgcolor.blend(colors::white, 0.1);
}
graph.rectangle(true, bgcolor);
}
virtual void item(graph_reference graph, const item_t& m, bool active, state_t sta)
{
const nana::rectangle & r = m.r;
color bgcolor;
color blcolor;
color dark_bgcolor;
if(m.bgcolor.invisible())
{
bgcolor = bgcolor_;
blcolor = blcolor_;
dark_bgcolor = dark_bgcolor_;
}
else
{
bgcolor = m.bgcolor;
blcolor = m.bgcolor.blend(colors::black, 0.5);
dark_bgcolor = m.bgcolor.blend(colors::black, 0.1);
}
auto round_r = r;
round_r.height += 2;
graph.round_rectangle(round_r, 3, 3, blcolor, true, colors::white);
auto beg = bgcolor;
auto end = dark_bgcolor;
if(active)
{
if (m.bgcolor.invisible())
beg = ilcolor_;
else
beg = m.bgcolor.blend(colors::white, 0.5);
end = bgcolor;
}
if (sta == item_renderer::highlight)
beg = beg.blend(colors::white, 0.5);
graph.gradual_rectangle(round_r.pare_off(2), beg, end, true);
}
virtual void add(graph_reference graph, const nana::rectangle& r, state_t sta)
{
int x = r.x + (static_cast<int>(r.width) - 14) / 2;
int y = r.y + (static_cast<int>(r.height) - 14) / 2;
::nana::color clr;
switch(sta)
{
case item_renderer::highlight:
clr = colors::white; break;
case item_renderer::press:
clr = static_cast<color_rgb>(0xA0A0A0); break;
case item_renderer::disable:
clr = static_cast<color_rgb>(0x808080); break;
default:
clr = static_cast<color_rgb>(0xF0F0F0);
}
graph.rectangle(r, true, bgcolor_);
facade<element::cross> cross;
cross.draw(graph, {}, clr, { x, y, 14, 6 }, element_state::normal);
}
virtual void close(graph_reference graph, const nana::rectangle& r, state_t sta)
{
facade<element::x_icon> x_icon;
x_icon.draw(graph, {}, colors::black, { r.x + static_cast<int>(r.width - 16) / 2, r.y + static_cast<int>(r.height - 16) / 2, 16, 16 }, element_state::normal);
if(item_renderer::highlight == sta)
graph.rectangle(r, false, static_cast<color_rgb>(0xa0a0a0));
}
virtual void close_fly(graph_reference graph, const nana::rectangle& r, bool active, state_t sta)
{
using namespace nana::paint;
::nana::color clr{ colors::black };
if (sta == item_renderer::highlight)
{
::nana::color bgcolor{ static_cast<color_rgb>(0xCCD2DD) };
::nana::color rect_clr{ static_cast<color_rgb>(0x9da3ab) };
graph.round_rectangle(r, 1, 1, rect_clr, false, {});
nana::rectangle draw_r(r);
graph.rectangle(draw_r.pare_off(1), false, rect_clr.blend(bgcolor, 0.2));
graph.rectangle(draw_r.pare_off(1), false, rect_clr.blend(bgcolor, 0.6));
graph.rectangle(draw_r.pare_off(1), false, rect_clr.blend(bgcolor, 0.8));
}
else if (!active)
clr = static_cast<color_rgb>(0x9299a4);
facade<element::x_icon> x_icon;
x_icon.draw(graph, {}, colors::black, { r.x + static_cast<int>(r.width - 16) / 2, r.y + static_cast<int>(r.height - 16) / 2, 16, 16 }, element_state::normal);
}
virtual void back(graph_reference graph, const nana::rectangle& r, state_t sta)
{
_m_draw_arrow(graph, r, sta, direction::west);
}
virtual void next(graph_reference graph, const nana::rectangle& r, state_t sta)
{
_m_draw_arrow(graph, r, sta, direction::east);
}
virtual void list(graph_reference graph, const nana::rectangle& r, state_t sta)
{
_m_draw_arrow(graph, r, sta, direction::south);
}
private:
void _m_draw_arrow(graph_reference graph, const nana::rectangle& r, state_t sta, ::nana::direction dir)
{
facade<element::arrow> arrow("solid_triangle");
arrow.direction(dir);
colors fgcolor = colors::black;
if (item_renderer::disable == sta)
{
arrow.switch_to("hollow_triangle");
fgcolor = colors::gray;
}
auto arrow_r = r;
arrow_r.x += static_cast<int>(arrow_r.width - 16) / 2;
arrow_r.y += static_cast<int>(arrow_r.height - 16) / 2;
arrow_r.width = arrow_r.height = 16;
arrow.draw(graph, bgcolor_, fgcolor, arrow_r, element_state::normal);
if(item_renderer::highlight == sta)
graph.rectangle(r, false, colors::dark_gray);
}
private:
::nana::color bgcolor_;
::nana::color dark_bgcolor_;
::nana::color blcolor_;
::nana::color ilcolor_;
};
class toolbox
{
struct button_tag
{
bool visible;
bool enable;
};
public:
enum button_t{ButtonAdd, ButtonScrollBack, ButtonScrollNext, ButtonList, ButtonClose, ButtonSize};
toolbox()
: close_fly_(false)
{
for(int i = ButtonAdd; i < ButtonSize; ++i)
{
buttons_[i].visible = true;
buttons_[i].enable = true;
}
buttons_[0].enable = false;
buttons_[3].enable = false;
buttons_[4].enable = false;
}
nana::rectangle area(button_t btn, unsigned height) const
{
nana::rectangle r(-1, 0, 0, 0);
if((btn == ButtonAdd) || (btn == ButtonClose && close_fly_))
return r;
int x = 0;
for(int i = ButtonScrollBack; i < ButtonSize; ++i)
{
if(btn != i)
{
if(i != ButtonAdd && buttons_[i].visible && buttons_[i].enable)
x += item_pixels();
}
else
{
r.x = x;
r.width = item_pixels();
r.height = height;
return r;
}
}
return r;
}
bool renderable(button_t btn) const
{
if(ButtonAdd <= btn && btn < ButtonSize)
{
if((btn == ButtonClose) && close_fly_)
return false;
return (buttons_[btn].visible && buttons_[btn].enable);
}
return false;
}
bool visible(button_t btn, bool vs)
{
if(buttons_[btn].visible != vs)
{
buttons_[btn].visible = vs;
return true;
}
return false;
}
bool visible(button_t btn) const
{
return buttons_[btn].visible;
}
bool close_fly(bool fly)
{
if(close_fly_ != fly)
{
close_fly_ = fly;
return true;
}
return false;
}
bool close_fly() const
{
return close_fly_;
}
bool enable(button_t btn) const
{
return buttons_[btn].enable;
}
bool enable(button_t btn, bool enb)
{
if(buttons_[btn].enable != enb)
{
buttons_[btn].enable = enb;
return true;
}
return false;
}
unsigned width() const
{
unsigned pixels = 0;
for(int i = ButtonScrollBack; i < ButtonSize; ++i)
{
if(renderable(static_cast<button_t>(i)))
{
if((i != ButtonClose) || (close_fly_ == false))
pixels += item_pixels();
}
}
return pixels;
}
unsigned item_pixels() const
{
return 18;
}
button_t which(int x) const
{
for(int i = ButtonAdd; i < ButtonSize; ++i)
{
if((i == ButtonClose && close_fly_) || (i == ButtonAdd))
continue;
if(renderable(static_cast<button_t>(i)))
{
if(0 <= x && x < static_cast<int>(item_pixels()))
return static_cast<button_t>(i);
else
x -= static_cast<int>(item_pixels());
}
}
return ButtonSize;
}
private:
bool close_fly_;
button_tag buttons_[ButtonSize];
};
//
class layouter
{
public:
typedef std::list<item_t>::iterator iterator;
typedef std::list<item_t>::const_iterator const_iterator;
nana::any& at(std::size_t i)
{
if(i < list_.size())
return at_no_bound_check(i);
throw std::out_of_range("invalid position of tabbar");
}
iterator iterator_at(std::size_t pos)
{
auto i = list_.begin();
std::advance(i, pos);
return i;
}
const_iterator iterator_at(std::size_t pos) const
{
auto i = list_.cbegin();
std::advance(i, pos);
return i;
}
nana::any& at_no_bound_check(std::size_t pos)
{
return iterator_at(pos)->value;
}
const nana::any& at(std::size_t pos) const
{
if(pos < list_.size())
return at_no_bound_check(pos);
throw std::out_of_range("invalid position of tabbar");
}
const nana::any& at_no_bound_check(std::size_t pos) const
{
return iterator_at(pos)->value;
}
toolbox & toolbox_object()
{
return toolbox_;
}
window widget_handle() const
{
return basis_.wd;
}
void attach(window wd, nana::paint::graphics& graph)
{
basis_.wd = wd;
basis_.graph = &graph;
}
void detach()
{
basis_.graph = 0;
}
const pat::cloneable<item_renderer> & ext_renderer() const
{
return basis_.renderer;
}
void ext_renderer(const pat::cloneable<item_renderer>& ir)
{
basis_.renderer = ir;
}
void event_agent(event_agent_interface* evt)
{
evt_agent_ = evt;
}
void insert(std::size_t pos, native_string_type&& text, nana::any&& value)
{
if (pos >= list_.size())
pos = list_.size();
list_.emplace(iterator_at(pos), std::move(text), std::move(value));
this->activate(pos);
render();
}
std::size_t length() const
{
return list_.size();
}
bool erase(std::size_t pos)
{
if(pos < list_.size())
{
bool close_attach = true;
if ((nullptr == evt_agent_) || evt_agent_->removed(pos, close_attach))
{
if (close_attach)
API::close_window(iterator_at(pos)->relative);
else
API::show_window(iterator_at(pos)->relative, false);
list_.erase(iterator_at(pos));
_m_adjust();
if(pos < basis_.active)
{
--basis_.active;
if(basis_.scroll_pixels > basis_.item_pixels)
basis_.scroll_pixels -= basis_.item_pixels;
else
basis_.scroll_pixels = 0;
}
else
{
auto count = list_.size();
if (pos == count)
basis_.active = pos = count - 1;
count *= basis_.item_pixels;
if (count > static_cast<unsigned>(_m_itembar_right()))
basis_.scroll_pixels = static_cast<unsigned>(count)-static_cast<unsigned>(_m_itembar_right());
else
basis_.scroll_pixels = 0;
}
if (basis_.active != ::nana::npos)
API::show_window(iterator_at(basis_.active)->relative, true);
if(evt_agent_)
evt_agent_->activated(basis_.active);
return true;
}
}
return false;
}
void render()
{
_m_adjust();
_m_render();
}
bool press()
{
trace_.state = item_renderer::press;
return (trace_.what != trace_.null);
}
bool active_by_trace()
{
return ((trace_.what == trace_.item) && (trace_.item_part != trace_.close)? activate(trace_.u.index) : false);
}
bool release()
{
trace_.state = item_renderer::highlight;
return true;
}
bool leave()
{
trace_.state = item_renderer::normal;
if(trace_.what != trace_.null)
{
trace_.what = trace_.null;
return true;
}
return false;
}
void track()
{
int left, right;
if(_m_item_pos(basis_.active, left, right))
{
if(left < 0)
basis_.scroll_pixels -= static_cast<unsigned>(-left);
else if(right > _m_itembar_right())
basis_.scroll_pixels += static_cast<unsigned>(right - _m_itembar_right());
}
}
bool trace(int x, int y)
{
trace_.state = item_renderer::highlight;
if(basis_.graph == 0) return false;
int ibar_end = _m_itembar_right();
trace_tag::item_t item_part = trace_.item_part;
std::size_t index = _m_where_itembar(x, y, ibar_end);
if(index != npos)
{
if((trace_.what != trace_.item) || (trace_.u.index != index) || (item_part != trace_.item_part))
{
trace_.what = trace_.item;
trace_.u.index = index;
return true;
}
return false;
}
if(toolbox_.renderable(toolbox_.ButtonAdd))
{
if(ibar_end <= x && x < ibar_end + static_cast<int>(toolbox_.item_pixels()))
{
if((trace_.what != trace_.toolbox) || (trace_.u.button != toolbox::ButtonAdd))
{
trace_.what = trace_.toolbox;
trace_.u.button = toolbox::ButtonAdd;
return true;
}
return false;
}
}
int tbpos = _m_toolbox_pos();
if(tbpos <= x)
{
toolbox::button_t t = toolbox_.which(x - tbpos);
if(trace_.what != trace_.toolbox || trace_.u.button != t)
{
trace_.what = trace_.toolbox;
trace_.u.button = t;
return true;
}
return false;
}
if(trace_.what != trace_.null)
{
trace_.what = trace_.null;
return true;
}
return false;
}
bool activate(std::size_t pos)
{
if(pos < list_.size() && (pos != basis_.active))
{
auto & tab_act = *iterator_at(pos);
API::show_window(tab_act.relative, true);
if (basis_.active < list_.size())
{
auto & tab_deact = *iterator_at(basis_.active);
//Don't hide the relative window if it is equal to active relative window.
//The tabbar allows a window to be attached to multiple tabs(pass false to DropOther of attach method)
if (tab_deact.relative != tab_act.relative)
API::show_window(tab_deact.relative, false);
}
basis_.active = pos;
track();
if(evt_agent_)
evt_agent_->activated(pos);
return true;
}
return false;
}
std::size_t active() const
{
return basis_.active;
}
window attach(std::size_t pos, window wd, bool drop_other)
{
if (pos >= list_.size())
throw std::out_of_range("tabbar: invalid position");
auto & tab = *iterator_at(pos);
auto old = tab.relative;
if (drop_other)
{
//Drop the relative windows which are equal to wd.
for (auto & t : list_)
{
if (wd == t.relative)
t.relative = nullptr;
}
}
tab.relative = wd;
API::show_window(wd, basis_.active == pos);
return old;
}
bool tab_color(std::size_t pos, bool is_bgcolor, const ::nana::color& clr)
{
if (pos >= list_.size())
throw std::out_of_range("tabbar: invalid position");
auto & m = *iterator_at(pos);
auto & m_clr = (is_bgcolor ? m.bgcolor : m.fgcolor);
if (m_clr != clr)
{
m_clr = clr;
return true;
}
return false;
}
void tab_image(std::size_t pos, const nana::paint::image& img)
{
if (pos >= list_.size())
throw std::out_of_range("tabbar: invalid position");
auto & m = *iterator_at(pos);
if(img)
m.img = img;
else
m.img.close();
}
bool text(std::size_t pos, const native_string_type& str)
{
if (pos >= list_.size())
throw std::out_of_range("tabbar: invalid position");
auto & m = *iterator_at(pos);
if(m.text != str)
{
m.text = str;
return true;
}
return false;
}
native_string_type text(std::size_t pos) const
{
if (pos >= list_.size())
throw std::out_of_range("tabbar: invalid position");
return iterator_at(pos)->text;
}
bool toolbox_answer(const arg_mouse& arg)
{
if(trace_.what == trace_.toolbox)
{
if(toolbox_.renderable(trace_.u.button))
{
switch(trace_.u.button)
{
case toolbox::ButtonAdd:
if(event_code::mouse_up == arg.evt_code)
return _m_add_tab(npos);
break;
case toolbox::ButtonScrollBack:
if(event_code::mouse_down == arg.evt_code)
return _m_scroll(true);
break;
case toolbox::ButtonScrollNext:
if(event_code::mouse_down == arg.evt_code)
return _m_scroll(false);
break;
case toolbox::ButtonList:
if (event_code::mouse_down == arg.evt_code)
{
_m_open_menulister();
return true;
}
break;
case toolbox::ButtonClose:
if (event_code::mouse_up == arg.evt_code)
{
if(this->erase(basis_.active))
{
track();
return true;
}
}
break;
default:
break;
}
}
}
else if((trace_.what == trace_.item) && (trace_.item_part == trace_.close))
{
if(event_code::mouse_up == arg.evt_code)
{
if(this->erase(trace_.u.index))
{
track();
trace(arg.pos.x, arg.pos.y);
return true;
}
}
}
return false;
}
private: //Fundation
bool _m_nextable() const
{
return (basis_.scroll_pixels + _m_itembar_right() < basis_.item_pixels * list_.size());
}
bool _m_add_tab(std::size_t pos)
{
if((pos == npos) || (pos >= list_.size()))
{
this->list_.emplace_back();
pos = list_.size() - 1;
}
else
list_.emplace(iterator_at(pos));
basis_.active = pos;
if(evt_agent_)
{
evt_agent_->added(pos);
evt_agent_->activated(pos);
}
return true;
}
bool _m_scroll(bool left)
{
if(left)
{
if(basis_.scroll_pixels)
{
unsigned i = basis_.scroll_pixels / basis_.item_pixels;
if(i > 1)
basis_.scroll_pixels = (i - 1) * basis_.item_pixels;
else
basis_.scroll_pixels = 0;
return true;
}
}
else
{
auto scale = static_cast<unsigned>(_m_itembar_right());
auto take = static_cast<unsigned>(list_.size() * basis_.item_pixels);
if(take > scale)
{
auto i = (basis_.scroll_pixels + scale) / basis_.item_pixels;
i += (basis_.scroll_pixels % basis_.item_pixels ? 2 : 1);
auto px = i * basis_.item_pixels;
if(px > take) px = take;
basis_.scroll_pixels = px - scale;
return true;
}
}
return false;
}
void _m_open_menulister()
{
menulister_.clear();
auto fn = [this](::nana::menu::item_proxy& ipx)
{
if (this->activate(ipx.index()))
API::refresh_window(basis_.wd);
};
for(auto & m : list_)
menulister_.append(to_utf8(m.text), fn);
auto r = toolbox_.area(toolbox_.ButtonList, basis_.graph->height());
r.x += _m_toolbox_pos();
menulister_.popup(basis_.wd, r.x, r.bottom());
}
//the begin pos of toolbox
int _m_toolbox_pos() const
{
int tbpos = static_cast<int>(basis_.graph->width()) - static_cast<int>(_m_toolbox_pixels());
return (tbpos < 0 ? 0 : tbpos);
}
unsigned _m_toolbox_pixels() const
{
return toolbox_.width();
}
int _m_itembar_right() const
{
int right = _m_toolbox_pos();
if(toolbox_.renderable(toolbox_.ButtonAdd))
right -= static_cast<int>(toolbox_.item_pixels());
int end = static_cast<int>(list_.size() * basis_.item_pixels);
return (end < right ? end : right);
}
nana::rectangle _m_close_fly_area(int x)
{
return nana::rectangle(x + basis_.item_pixels - 18, (basis_.graph->height() - 14) / 2, 14, 14);
}
bool _m_item_pos(std::size_t index, int &left, int &right) const
{
if(index < list_.size())
{
left = static_cast<int>(index * basis_.item_pixels);
left -= static_cast<int>(basis_.scroll_pixels);
right = left + basis_.item_pixels;
return true;
}
return false;
}
std::size_t _m_where_itembar(int x, int y, int end)
{
if(x < 0 || x >= end) return npos;
int left = -static_cast<int>(basis_.scroll_pixels);
std::size_t i = 0, size = list_.size();
for(; i != size; ++i)
{
if(left >= end)
{
i = npos;
break;
}
if(left <= x && x < left + static_cast<int>(basis_.item_pixels))
break;
left += basis_.item_pixels;
}
if(i < list_.size())
{
trace_.item_part = trace_.body;
if(toolbox_.close_fly())
{
nana::rectangle r = _m_close_fly_area(left);
if((r.x <= x && x < r.x + static_cast<int>(r.width)) && (r.y <= y && y < r.y + static_cast<int>(r.height)))
trace_.item_part = trace_.close;
}
return i;
}
return npos;
}
nana::rectangle _m_toolbox_area(toolbox::button_t btn) const
{
nana::rectangle r(0, 0, toolbox_.item_pixels(), basis_.graph->height());
if(btn == toolbox_.ButtonAdd)
{
int end = _m_itembar_right();
if(static_cast<int>(list_.size() * basis_.item_pixels) < end)
r.x = static_cast<int>(list_.size() * basis_.item_pixels);
else
r.x = end;
}
else if(toolbox_.ButtonClose == btn && toolbox_.close_fly())
{
r.x = -1;
r.width = r.height = 0;
}
else
r = toolbox_.area(btn, basis_.graph->height());
return r;
}
void _m_adjust()
{
if((nullptr == basis_.graph) || (0 == list_.size())) return;
//adjust the number of pixels of item.
bool scrollable = toolbox_.renderable(toolbox_.ButtonScrollBack);
if(scrollable)
{
toolbox_.visible(toolbox_.ButtonScrollBack, false);
toolbox_.visible(toolbox_.ButtonScrollNext, false);
}
unsigned beside = _m_toolbox_pixels();
if(toolbox_.renderable(toolbox_.ButtonAdd))
beside += toolbox_.item_pixels();
unsigned pixels = basis_.graph->width();
if(pixels <= beside)
return;
unsigned each_pixels = static_cast<unsigned>((pixels - beside) / list_.size());
if(each_pixels > basis_.max_pixels)
each_pixels = basis_.max_pixels;
else if(each_pixels < basis_.min_pixels)
each_pixels = basis_.min_pixels;
unsigned total = static_cast<unsigned>(each_pixels * list_.size());
if(total > pixels - beside && toolbox_.enable(toolbox_.ButtonScrollBack))
{
toolbox_.visible(toolbox_.ButtonScrollBack, true);
toolbox_.visible(toolbox_.ButtonScrollNext, true);
beside = _m_toolbox_pixels();
if(toolbox_.renderable(toolbox_.ButtonAdd))
beside += toolbox_.item_pixels();
if(pixels <= beside)
return;
each_pixels = static_cast<unsigned>((pixels - beside) / list_.size());
if(each_pixels > basis_.max_pixels)
each_pixels = basis_.max_pixels;
else if(each_pixels < basis_.min_pixels)
each_pixels = basis_.min_pixels;
}
else
basis_.scroll_pixels = 0;
if(each_pixels != basis_.item_pixels)
basis_.item_pixels = each_pixels;
if(scrollable != toolbox_.renderable(toolbox_.ButtonScrollBack))
basis_.scroll_pixels = static_cast<unsigned>(list_.size() * basis_.item_pixels) - _m_itembar_right();
}
item_renderer::state_t _m_state(unsigned index) const
{
return ((trace_.what == trace_.item) && (trace_.u.index == index) ? item_renderer::highlight : item_renderer::normal);
}
item_renderer::state_t _m_state(toolbox::button_t kind) const
{
return ((trace_.what == trace_.toolbox && trace_.u.button == kind) ? item_renderer::highlight : item_renderer::normal);
}
void _m_render()
{
if(!basis_.renderer || (nullptr == basis_.graph))
return;
auto bgcolor = API::bgcolor(basis_.wd);
auto fgcolor = API::fgcolor(basis_.wd);
item_renderer::item_t m;
m.r = ::nana::rectangle{ basis_.graph->size() };
basis_.renderer->background(*basis_.graph, m.r, bgcolor);
//the max number of pixels of tabs.
int pixels = static_cast<int>(m.r.width - _m_toolbox_pixels());
m.r.x = -static_cast<int>(basis_.scroll_pixels);
m.r.width = basis_.item_pixels;
unsigned index = 0;
bool is_close_fly = toolbox_.visible(toolbox_.ButtonClose) && toolbox_.enable(toolbox_.ButtonClose) && toolbox_.close_fly();
item_renderer::item_t active_m;
for(auto i = list_.cbegin(); i != list_.cend(); ++i, ++index)
{
if(m.r.x >= pixels) break;
if(m.r.x + static_cast<int>(basis_.item_pixels) > 0)
{
m.bgcolor = i->bgcolor;
m.fgcolor = i->fgcolor;
if(index == this->basis_.active)
active_m = m;
const item_t & item = *i;
basis_.renderer->item(*basis_.graph, m, (index == basis_.active), _m_state(index));
if(is_close_fly)
{
item_renderer::state_t sta = item_renderer::normal;
if(trace_.what == trace_.item && trace_.item_part == trace_.close && index == trace_.u.index)
sta = item_renderer::highlight;
basis_.renderer->close_fly(*basis_.graph, _m_close_fly_area(m.r.x), (index == basis_.active), sta);
}
if(false == item.img.empty())
item.img.stretch(::nana::rectangle{ item.img.size() }, *basis_.graph, nana::rectangle(m.r.x + 4, (m.r.height - 16) / 2, 16, 16));
if(item.text.size())
{
nana::size ts = basis_.graph->text_extent_size(item.text);
basis_.graph->palette(true, m.fgcolor.invisible() ? fgcolor : m.fgcolor);
nana::paint::text_renderer tr(*basis_.graph);
std::wstring wtext = to_wstring(item.text);
tr.render({ m.r.x + 24, m.r.y + static_cast<int>(m.r.height - ts.height) / 2 },
wtext.c_str(), wtext.length(), basis_.item_pixels - 24 - 18, true);
}
}
m.r.x += static_cast<int>(basis_.item_pixels);
}
_m_render_toolbox(bgcolor);
int bottom = static_cast<int>(basis_.graph->height()) - 1;
if(_m_nextable())
{
int x = _m_itembar_right();
if (x > 0)
{
basis_.graph->line({ x - 2, 0 }, { x - 2, bottom }, static_cast<color_rgb>(0x808080));
basis_.graph->line({ x - 1, 0 }, { x - 1, bottom }, static_cast<color_rgb>(0xf0f0f0));
}
}
basis_.graph->palette(false, static_cast<color_rgb>(0x808080));
int right = static_cast<int>(basis_.graph->width());
int end = active_m.r.x + static_cast<int>(active_m.r.width);
if(0 < active_m.r.x && active_m.r.x < right)
basis_.graph->line({ 0, bottom }, { active_m.r.x, bottom });
if(0 <= end && end < right)
basis_.graph->line({ end, bottom }, { right, bottom });
}
void _m_render_toolbox(const ::nana::color& bgcolor)
{
bool backable = (basis_.scroll_pixels != 0);
int xbase = _m_toolbox_pos();
basis_.graph->rectangle({ xbase, 0, _m_toolbox_pixels(), basis_.graph->height() }, true, bgcolor);
for(int i = toolbox::ButtonAdd; i < toolbox::ButtonSize; ++i)
{
toolbox::button_t btn = static_cast<toolbox::button_t>(i);
if(toolbox_.renderable(btn) == false) continue;
nana::rectangle r = _m_toolbox_area(btn);
if(toolbox_.ButtonAdd != btn)
r.x += xbase;
item_renderer::state_t state = item_renderer::normal;
if((trace_.what == trace_.toolbox) && (trace_.u.button == btn))
state = trace_.state;
switch(btn)
{
case toolbox::ButtonScrollBack:
basis_.renderer->back(*basis_.graph, r, (backable ? state : item_renderer::disable));
break;
case toolbox::ButtonScrollNext:
basis_.renderer->next(*basis_.graph, r, (_m_nextable() ? state : item_renderer::disable));
break;
case toolbox::ButtonList:
basis_.renderer->list(*basis_.graph, r, state);
break;
case toolbox::ButtonClose:
basis_.renderer->close(*basis_.graph, r, state);
break;
case toolbox::ButtonAdd:
basis_.renderer->add(*basis_.graph, r, state);
break;
default:
break;
}
}
}
private:
std::list<item_t> list_;
event_agent_interface* evt_agent_ = nullptr;
toolbox toolbox_;
::nana::menu menulister_;
struct trace_tag
{
enum t{null, item, toolbox};
enum item_t{body, close};
t what;
item_t item_part; //it is valid while "what" is item.
item_renderer::state_t state;
union
{
std::size_t index;
toolbox::button_t button;
}u;
trace_tag():what(null), state(item_renderer::normal)
{}
}trace_;
struct basis_tag
{
window wd{nullptr};
nana::paint::graphics * graph{nullptr};
pat::cloneable<item_renderer> renderer;
unsigned max_pixels{250};
unsigned min_pixels{100};
unsigned item_pixels{max_pixels};
unsigned scroll_pixels{0};
std::size_t active{npos};
basis_tag():renderer{ def_renderer() }
{}
}basis_;
};
//class trigger
trigger::trigger()
: layouter_(new layouter)
{}
trigger::~trigger()
{
delete layouter_;
}
void trigger::activate(std::size_t pos)
{
if(layouter_->activate(pos))
API::refresh_window(layouter_->widget_handle());
}
std::size_t trigger::activated() const
{
return layouter_->active();
}
nana::any& trigger::at(std::size_t i) const
{
return layouter_->at(i);
}
nana::any& trigger::at_no_bound_check(std::size_t i) const
{
return layouter_->at_no_bound_check(i);
}
const pat::cloneable<item_renderer> & trigger::ext_renderer() const
{
return layouter_->ext_renderer();
}
void trigger::ext_renderer(const pat::cloneable<item_renderer>& ir)
{
layouter_->ext_renderer(ir);
}
void trigger::set_event_agent(event_agent_interface* evt)
{
layouter_->event_agent(evt);
}
void trigger::insert(std::size_t pos, native_string_type&& text, nana::any&& value)
{
layouter_->insert(pos, std::move(text), std::move(value));
}
std::size_t trigger::length() const
{
return layouter_->length();
}
bool trigger::close_fly(bool fly)
{
return layouter_->toolbox_object().close_fly(fly);
}
window trigger::attach(std::size_t pos, window wd, bool drop_other)
{
return layouter_->attach(pos, wd, drop_other);
}
void trigger::erase(std::size_t pos)
{
layouter_->erase(pos);
}
void trigger::tab_color(std::size_t i, bool is_bgcolor, const ::nana::color& clr)
{
if(layouter_->tab_color(i, is_bgcolor, clr))
API::refresh_window(layouter_->widget_handle());
}
void trigger::tab_image(std::size_t i, const nana::paint::image& img)
{
layouter_->tab_image(i, img);
API::refresh_window(layouter_->widget_handle());
}
void trigger::text(std::size_t i, const native_string_type& str)
{
if(layouter_->text(i, str))
API::refresh_window(layouter_->widget_handle());
}
native_string_type trigger::text(std::size_t i) const
{
return layouter_->text(i);
}
bool trigger::toolbox(kits btn, bool enable)
{
auto tb = toolbox::ButtonSize;
auto& tbox = layouter_->toolbox_object();
switch(btn)
{
case kits::add:
tb = toolbox::ButtonAdd; break;
case kits::list:
tb = toolbox::ButtonList; break;
case kits::close:
tb = toolbox::ButtonClose; break;
case kits::scroll:
tbox.enable(toolbox::ButtonScrollBack, enable);
return tbox.enable(tbox.ButtonScrollNext, enable);
}
return (tb != toolbox::ButtonSize ? tbox.enable(tb, enable) : false);
}
void trigger::attached(widget_reference widget, graph_reference graph)
{
layouter_->attach(widget, graph);
}
void trigger::detached()
{
layouter_->detach();
}
void trigger::refresh(graph_reference)
{
layouter_->render();
}
void trigger::mouse_down(graph_reference, const arg_mouse& arg)
{
if(layouter_->press())
{
if(false == layouter_->active_by_trace())
layouter_->toolbox_answer(arg);
layouter_->render();
API::dev::lazy_refresh();
}
}
void trigger::mouse_up(graph_reference, const arg_mouse& arg)
{
bool rd = layouter_->release();
rd |= layouter_->toolbox_answer(arg);
if(rd)
{
layouter_->render();
API::dev::lazy_refresh();
}
}
void trigger::mouse_move(graph_reference, const arg_mouse& arg)
{
if(layouter_->trace(arg.pos.x, arg.pos.y))
{
layouter_->render();
API::dev::lazy_refresh();
}
}
void trigger::mouse_leave(graph_reference, const arg_mouse&)
{
if(layouter_->leave())
{
layouter_->render();
API::dev::lazy_refresh();
}
}
//end class trigger
}//end namespace tabbar
}//end namespace drawerbase
}//end namespace nana
#include <forward_list>
namespace nana
{
namespace drawerbase
{
namespace tabbar_lite
{
struct item
{
::std::string text;
::nana::any value;
::std::pair<int, int> pos_ends;
::nana::window attached_window{ nullptr };
item(std::string t, ::nana::any v)
: text(std::move(t)), value(std::move(v))
{}
};
void calc_px(std::vector<unsigned>& values, unsigned limit)
{
unsigned base = 0;
auto count = values.size();
while (count)
{
unsigned minv = static_cast<unsigned>(-1);
unsigned minv_count = 0;
for (auto u : values)
{
if (u >= base && u < minv)
{
minv_count = 1;
minv = u;
}
else if (minv == u)
minv_count++;
}
count -= minv_count;
if (minv * count >= limit)
{
auto piece = limit / double(count);
double deviation = 0;
for (auto & u : values)
{
if (minv >= u)
{
auto px = piece + deviation;
u = static_cast<unsigned>(px);
deviation = px - u;
}
}
return;
}
base = minv;
limit -= minv_count * minv;
}
}
class model
{
struct indexes
{
std::size_t hovered_pos{ npos };
std::size_t active_pos{ 0 };
};
public:
using graph_reference = ::nana::paint::graphics&;
static const std::size_t npos = static_cast<std::size_t>(-1);
void set_widget(::nana::tabbar_lite& wdg)
{
widget_ = &wdg;
}
::nana::tabbar_lite* widget_ptr() const
{
return widget_;
}
std::forward_list<item>& items()
{
return items_;
}
void show_attached_window()
{
if (indexes_.active_pos != npos)
{
auto i = items_.cbegin();
std::advance(i, indexes_.active_pos);
API::show_window(i->attached_window, true);
std::size_t pos = 0;
for (auto & m : items_)
{
if (pos++ != indexes_.active_pos)
API::show_window(m.attached_window, false);
}
}
}
bool track_pointer(const point& pos)
{
std::size_t item_pos = 0;
bool matched = false;
for (auto & m : items_)
{
if (m.pos_ends.first <= pos.x && pos.x < m.pos_ends.second)
{
matched = true;
break;
}
++item_pos;
}
if (!matched)
item_pos = npos;
if (indexes_.hovered_pos == item_pos)
return false;
indexes_.hovered_pos = item_pos;
return true;
}
indexes& get_indexes()
{
return indexes_;
}
private:
::nana::tabbar_lite * widget_{ nullptr };
std::forward_list<item> items_;
indexes indexes_;
};
class renderer
{
public:
using graph_reference = ::nana::paint::graphics&;
void render(graph_reference graph, model& model)
{
_m_calc_metrics(graph, model.items());
auto & scheme = model.widget_ptr()->scheme();
//draw background
graph.rectangle(true, scheme.background);
auto & indexes = model.get_indexes();
std::size_t pos = 0;
for (auto & m : model.items())
{
rectangle r{ m.pos_ends.first, 0, static_cast<unsigned>(m.pos_ends.second - m.pos_ends.first), graph.height() };
if (indexes.active_pos == pos)
{
graph.palette(false, colors::white);
graph.palette(true, colors::black);
}
else
{
::nana::color bgcolor(colors::coral);
if (pos == indexes.hovered_pos)
bgcolor = bgcolor.blend(colors::white, 0.5);
graph.palette(false, bgcolor);
graph.palette(true, colors::white);
}
graph.rectangle(r, true);
graph.bidi_string({ m.pos_ends.first + 5, 0 }, m.text.data(), m.text.size());
++pos;
}
}
private:
void _m_calc_metrics(graph_reference graph, std::forward_list<item>& items)
{
std::vector<unsigned> pxs;
unsigned pixels = 0;
for (auto & m : items)
{
auto ts = graph.bidi_extent_size(m.text);
pxs.emplace_back(ts.width + 12);
pixels += ts.width + 12;
}
if (pixels > graph.width())
calc_px(pxs, graph.width());
auto i = pxs.cbegin();
int pos = 0;
for (auto & m : items)
{
m.pos_ends.first = pos;
m.pos_ends.second = (pos += static_cast<int>(*i++));
}
}
};
//class driver
driver::driver()
: model_(new model)
{
}
driver::~driver()
{
delete model_;
}
model* driver::get_model() const noexcept
{
return model_;
}
void driver::attached(widget_reference wdg, graph_reference)
{
model_->set_widget(dynamic_cast<nana::tabbar_lite&>(wdg));
}
//Overrides drawer_trigger's method
void driver::refresh(graph_reference graph)
{
renderer rd;
rd.render(graph, *model_);
}
void driver::mouse_move(graph_reference graph, const arg_mouse& arg)
{
if (!model_->track_pointer(arg.pos))
return;
refresh(graph);
API::dev::lazy_refresh();
}
void driver::mouse_leave(graph_reference graph, const arg_mouse&)
{
if (model_->get_indexes().hovered_pos == model_->npos)
return;
refresh(graph);
API::dev::lazy_refresh();
}
void driver::mouse_down(graph_reference graph, const arg_mouse&)
{
auto & indexes = model_->get_indexes();
if ((indexes.hovered_pos == model_->npos) || (indexes.active_pos == indexes.hovered_pos))
return;
if (indexes.active_pos != indexes.hovered_pos)
{
indexes.active_pos = indexes.hovered_pos;
model_->show_attached_window();
refresh(graph);
API::dev::lazy_refresh();
event_arg arg;
model_->widget_ptr()->events().selected.emit(arg, model_->widget_ptr()->handle());
}
}
//end class driver
}
}
//class tabbar
tabbar_lite::tabbar_lite(window parent_wd, bool visible, const ::nana::rectangle& r)
{
this->create(parent_wd, r, visible);
}
//capacity
std::size_t tabbar_lite::length() const
{
auto& items = get_drawer_trigger().get_model()->items();
internal_scope_guard lock;
return static_cast<std::size_t>(std::distance(items.cbegin(), items.cend()));
}
//modifiers
void tabbar_lite::attach(std::size_t pos_set, window wd)
{
auto model = get_drawer_trigger().get_model();
internal_scope_guard lock;
for (auto & m : model->items())
{
if (0 == pos_set--)
{
m.attached_window = wd;
model->show_attached_window();
return;
}
}
throw std::out_of_range("invalid position of tabbar_lite");
}
window tabbar_lite::attach(std::size_t pos_set) const
{
auto model = get_drawer_trigger().get_model();
internal_scope_guard lock;
for (auto & m : model->items())
{
if (0 == pos_set--)
return m.attached_window;
}
throw std::out_of_range("invalid position of tabbar_lite");
}
void tabbar_lite::push_back(std::string text, ::nana::any any)
{
auto & items = get_drawer_trigger().get_model()->items();
internal_scope_guard lock;
auto i = items.cbefore_begin();
while (true)
{
auto next = i++;
if (i == items.cend())
{
items.emplace_after(next, std::move(text), std::move(any));
break;
}
}
API::refresh_window(handle());
}
void tabbar_lite::push_front(std::string text, ::nana::any any)
{
auto & items = get_drawer_trigger().get_model()->items();
internal_scope_guard lock;
items.emplace_front(std::move(text), std::move(any));
API::refresh_window(handle());
}
std::size_t tabbar_lite::selected() const
{
auto model = get_drawer_trigger().get_model();
internal_scope_guard lock;
return model->get_indexes().active_pos;
}
void tabbar_lite::erase(std::size_t pos, bool close_attached)
{
auto model = get_drawer_trigger().get_model();
internal_scope_guard lock;
const auto len = length();
if (len <= pos)
throw std::out_of_range("invalid position of tabbar_lite");
auto active_pos = model->get_indexes().active_pos;
//selection_changed is used to determine whether the title will be updated
bool selection_changed = true;
if (pos == active_pos)
{
if (active_pos + 1 == len)
{
if (active_pos)
--active_pos;
else
active_pos = npos;
}
}
else if (pos < active_pos)
--active_pos;
else
selection_changed = false;
model->get_indexes().active_pos = active_pos;
auto i = model->items().cbefore_begin();
std::advance(i, pos);
auto attached_wd = std::next(i)->attached_window;
model->items().erase_after(i);
model->show_attached_window();
API::refresh_window(handle());
if (close_attached && attached_wd)
API::close_window(attached_wd);
if (selection_changed && (active_pos != npos))
{
event_arg arg;
events().selected.emit(arg, handle());
}
}
//end class tabbar
}//end namespace nana