// Copyright (C) 2012-2015 ChaosForge Ltd // http://chaosforge.org/ // // This file is part of Nova libraries. // For conditions of distribution and use, see copying.txt file in root folder. #include "nv/gui/gui_environment.hh" #include "nv/gui/gui_renderer.hh" /* TODO: parse a lua stylesheet as per Trac wiki IDEA: Store everything in unordered_maps, with lua_value's? A lua_value is a variant stores strings as const char* that deletes them on destructor? Question is that there might not be much gained on speed anyway, due to Lua's speed. Special function field allows delayed per parse execution? */ nv::gui::environment::environment( renderer* r ) : m_renderer( r ) { r->set_environment( this ); m_screen = create_element( handle(), m_renderer->get_area() ); } void nv::gui::environment::load_style( const string_view& filename ) { m_renderer->load_style( filename ); } nv::gui::handle nv::gui::environment::create_element( const rectangle& r ) { return create_element( m_screen, r ); } nv::gui::handle nv::gui::environment::create_element( handle parent, const rectangle& r ) { if ( parent.is_nil() ) parent = m_screen; handle result = m_elements.create(); element* e = m_elements.get( result ); rectangle ar = r; rectangle full = m_renderer->get_area(); if ( ar.ul.x < 0 ) { ar.ul.x += full.lr.x; ar.lr.x += full.lr.x; } if ( ar.ul.y < 0 ) { ar.ul.y += full.lr.y; ar.lr.y += full.lr.y; } e->m_child_count = 0; e->m_flags[ENABLED] = true; e->m_flags[VISIBLE] = true; e->m_flags[DIRTY] = true; e->m_render_data = nullptr; e->m_absolute = ar; e->m_relative = ar; if ( !parent.is_nil() ) // screen creation add_child( parent, result ); return result; } void nv::gui::environment::destroy_element( handle e ) { element* dead_element = m_elements.get( e ); if ( dead_element == nullptr ) return; destroy_children( e ); remove_child( dead_element->m_parent, e ); delete dead_element->m_render_data; dead_element->m_render_data = nullptr; dead_element->m_parent = handle(); m_elements.destroy( e ); } void nv::gui::environment::update( handle e, uint32 elapsed ) { element* el = m_elements.get( e ); if ( !el ) return; // el->on_update( elapsed ); if ( el->m_flags[ENABLED] ) { bool hover = el->m_flags[HOVER]; bool new_hover = el->m_absolute.contains( m_mouse_position ); if ( hover != new_hover ) { el->m_flags[HOVER] = new_hover; // gain lose hover event m_renderer->on_hover_change( el ); } } if ( el->m_flags[VISIBLE] ) { for ( handle i : el->m_children ) { update( i, elapsed ); } } if ( el->m_flags[DIRTY] || el->m_render_data == nullptr ) { m_renderer->redraw( el, elapsed ); el->m_flags[DIRTY] = false; } } void nv::gui::environment::draw( handle e ) { element* el = m_elements.get( e ); if ( !el ) return; if ( el->m_flags[VISIBLE] ) { // el->on_draw(); m_renderer->draw( el ); for ( handle i : el->m_children ) { draw(i); } } } void nv::gui::environment::update() { update( m_screen, 0 ); } void nv::gui::environment::draw() { draw( m_screen ); m_renderer->draw(); } void nv::gui::environment::add_child( handle child ) { add_child( m_screen, child ); } void nv::gui::environment::add_child( handle parent, handle child ) { element* e = m_elements.get( child ); element* p = m_elements.get( parent ); if ( e && p ) { remove_child( e->m_parent, child ); e->m_parent = parent; p->m_children.push_back( child ); p->m_child_count++; } } void nv::gui::environment::destroy_children( handle e ) { element* parent = m_elements.get(e); if ( parent ) { while ( !parent->m_children.empty() ) { destroy_element( parent->m_children.back() ); } } } nv::gui::environment::~environment() { destroy_children( handle() ); delete m_renderer; } bool nv::gui::environment::process_io_event( const io_event& ev ) { switch ( ev.type ) { // case EV_KEY : case EV_MOUSE_BUTTON : if ( ev.mbutton.pressed && ev.mbutton.button == MOUSE_LEFT ) { handle h = get_element( position( ev.mbutton.x, ev.mbutton.y ) ); element* e = m_elements.get( h ); if (e) if ( e->m_on_click ) e->m_on_click(); set_selected( h ); return true; } return false; case EV_MOUSE_MOVE: m_mouse_position = position( ev.mmove.x, ev.mmove.y ); return false; // case EV_MOUSE_WHEEL : default: break; } return false; } nv::string_view nv::gui::environment::get_string( shash64 h ) { return m_strings[h]; } bool nv::gui::environment::set_selected( handle e ) { if ( e != m_selected ) { element* eold = m_elements.get( m_selected ); element* el = m_elements.get( e ); if ( eold ) { eold->m_flags[SELECTED] = false; m_renderer->on_select_change( eold ); } if ( el ) { el->m_flags[SELECTED] = true; m_renderer->on_select_change( el ); } m_selected = e; return true; } return false; } bool nv::gui::environment::process_io_event( handle e, const io_event& ev ) { element* el = m_elements.get( e ); return el && el->m_parent.is_valid() ? process_io_event( el->m_parent, ev ) : false; } nv::gui::handle nv::gui::environment::get_element( const position& p ) { return get_deepest_child( m_screen, p ); } nv::gui::handle nv::gui::environment::get_deepest_child( handle e, const position& p ) { element* el = m_elements.get(e); if ( !el && !el->m_flags[VISIBLE] ) return handle(); handle result; auto it = el->m_children.rbegin(); while ( it != el->m_children.rend() ) { result = get_deepest_child( *it, p ); if ( result.is_valid() ) return result; ++it; } if ( el->m_absolute.contains(p) ) return e; return handle(); } void nv::gui::environment::move_to_top( handle child ) { element* e = m_elements.get( child ); element* parent = m_elements.get( e->m_parent ); if ( e && parent ) { auto it = find( parent->m_children.begin(), parent->m_children.end(), child ); if ( it != parent->m_children.end() ) { parent->m_children.erase( it ); parent->m_children.push_back( child ); parent->m_flags[DIRTY] = true; } } } void nv::gui::environment::set_relative( handle e, const rectangle& r ) { element* el = m_elements.get(e); if ( el ) { el->m_flags[DIRTY] = true; el->m_relative = r; recalculate_absolute( e ); } } void nv::gui::environment::set_relative( handle e, const position& p ) { element* el = m_elements.get(e); if ( el ) { set_relative( e, rectangle( p, p + el->m_relative.get_size() ) ); } } void nv::gui::environment::recalculate_absolute( handle e ) { element* el = m_elements.get(e); rectangle pabsolute = m_elements.get( el->m_parent )->m_absolute; el->m_absolute = el->m_relative + pabsolute.ul; for ( handle o : el->m_children ) { recalculate_absolute( o ); } } void nv::gui::environment::set_class( handle e, const string_view& text ) { element* ep = m_elements.get(e); if ( ep != nullptr ) { ep->m_class = m_strings.insert( text ); ep->m_flags[DIRTY] = true; m_renderer->on_style_change( ep ); } } void nv::gui::environment::set_on_click( handle e, const function< void() >& on_click ) { element* ep = m_elements.get( e ); if ( ep != nullptr ) { ep->m_on_click = on_click; } } void nv::gui::environment::set_text( handle e, const string_twine& text ) { element* ep = m_elements.get(e); if ( ep != nullptr ) { // TODO : implement twine == operator ep->m_text.assign( text ); ep->m_flags[DIRTY] = true; } } void nv::gui::environment::remove_child( handle parent, handle child ) { element* p = m_elements.get( parent ); if ( p ) { auto it = find( p->m_children.begin(), p->m_children.end(), child ); if ( it != p->m_children.end() ) { element* e = m_elements.get( *it ); e->m_parent = handle(); p->m_children.erase( it ); } } }