Index: ps/trunk/binaries/data/mods/mod/gui/gui.rnc =================================================================== --- ps/trunk/binaries/data/mods/mod/gui/gui.rnc (revision 19352) +++ ps/trunk/binaries/data/mods/mod/gui/gui.rnc (revision 19353) @@ -1,282 +1,283 @@ namespace a = "http://relaxng.org/ns/compatibility/annotations/1.0" ## # NOTE: To modify this Relax NG grammar, edit the Relax NG Compact (.rnc) file # and use a converter tool like trang to generate the Relax NG XML (.rng) file ## start = object | objects | setup | sprites | styles ## # Types # ## # xsd:boolean could be used instead of this definition, # though it considers "1" & "0" as valid values. bool = "true" | "false" align = "left" | "center" | "right" valign = "top" | "center" | "bottom" wrapmode = "repeat" | "mirrored_repeat" | "clamp_to_edge" coord = xsd:string { pattern = "-?\d*\.?\d+%?([\+\-]\d*\.?\d+%?)*" } clientarea = list { coord, coord, coord, coord } # color can be a name or "R G B A" format string rgba = list { xsd:integer { minInclusive = "0" maxInclusive = "255" }, xsd:integer { minInclusive = "0" maxInclusive = "255" }, xsd:integer { minInclusive = "0" maxInclusive = "255" }, xsd:integer { minInclusive = "0" maxInclusive = "255" }?} ccolor = rgba | xsd:string { pattern = "[A-Za-z]+" } size = list { xsd:decimal, xsd:decimal } pos = list { xsd:decimal, xsd:decimal } rect = list { xsd:decimal, xsd:decimal, xsd:decimal, xsd:decimal } ## # Defines # ## unique_settings = attribute name { text }?, [ a:defaultValue = "empty" ] attribute type { text }?, attribute style { text }? # This could probably be made more specific/strict # with more information regarding the use/meaning # of these attributes. base_settings = attribute absolute { bool }?& attribute enable { bool }?& attribute ghost { bool }?& attribute hidden { bool }?& attribute size { clientarea }?& attribute z { xsd:decimal }? # Defaults are not put in here, because it ruins the concept of styles. ex_settings = attribute anchor { valign }?& attribute buffer_zone { xsd:decimal }?& attribute buffer_width { xsd:decimal }?& attribute button_width { xsd:decimal }?& attribute checked { bool }?& attribute clip { bool }?& attribute dropdown_size { xsd:decimal }?& attribute dropdown_buffer { xsd:decimal }?& attribute enabled { bool }?& attribute font { text }?& attribute fov_wedge_color { ccolor }?& attribute heading_height { text }?& attribute hotkey { text }?& attribute cell_id { xsd:integer }?& attribute independent { bool }?& attribute input_initvalue_destroyed_at_focus { bool }?& attribute mask { bool }?& attribute mask_char { xsd:string { minLength = "1" maxLength = "1" } }?& attribute max_length { xsd:nonNegativeInteger }?& attribute maxwidth { xsd:decimal }? & attribute multiline { bool }?& attribute offset { pos }?& attribute scrollbar { bool }?& attribute scrollbar_style { text }?& attribute scroll_bottom { bool }?& attribute scroll_top { bool }?& attribute selected_column { text }?& attribute selected_column_order { text }?& attribute sortable { bool }?& attribute sound_closed { text }?& attribute sound_disabled { text }?& attribute sound_enter { text }?& attribute sound_leave { text }?& attribute sound_opened { text }?& attribute sound_pressed { text }?& attribute sound_selected { text }?& attribute sprite { text }?& attribute sprite2 { text }?& attribute sprite_asc { text }?& attribute sprite_heading { text }?& attribute sprite_bar { text }?& attribute sprite_background { text }?& attribute sprite_desc { text }?& attribute sprite_disabled { text }?& attribute sprite_list { text }?& attribute sprite2_disabled { text }?& attribute sprite_not_sorted { text }?& attribute sprite_over { text }?& attribute sprite2_over { text }?& attribute sprite_pressed { text }?& attribute sprite2_pressed { text }?& attribute sprite_selectarea { text }?& attribute square_side { xsd:decimal }?& attribute textcolor { ccolor }?& attribute textcolor_disabled { ccolor }?& attribute textcolor_over { ccolor }?& attribute textcolor_pressed { ccolor }?& attribute textcolor_selected { ccolor }?& attribute text_align { align }?& attribute text_valign { valign }?& attribute tooltip { text }?& attribute tooltip_style { text }? ## # Objects # ## objects = element objects { (script | object)* } script = element script { text & attribute file { text }? & attribute directory { text }? } object = element object { ((object | action | \attribute | column | \include | item | repeat | translatableAttribute)* | text), unique_settings, base_settings, ex_settings } action = element action { text, attribute on { text }, attribute file { text }? } \attribute = element attribute { (keep | translate)*, attribute id { text } } column = element column { translatableAttribute?, ( attribute id { text }& attribute color { ccolor }?& attribute heading { text }?& - attribute width { text }? + attribute width { text }?& + attribute hidden { bool }? ) } \include = element include { attribute file { text }| attribute directory { text } } item = element item { text, attribute enabled { bool }? } keep = element keep { text } repeat = element repeat { object+, attribute count { xsd:nonNegativeInteger }, attribute var { text }? } translate = element translate { text } translatableAttribute = element translatableAttribute { text, ( attribute id { text }& attribute comment { text }?& attribute context { text }? ) } ## # Styles # ## styles = element styles { style* } style = element style { attribute name { text }, base_settings, ex_settings } ## # Setup # ## setup = element setup { (icon | scrollbar | tooltip | color)* } scrollbar = element scrollbar { attribute name { text }& attribute width { xsd:decimal }& attribute alwaysshown { bool }?& attribute maximum_bar_size { xsd:decimal }?& attribute minimum_bar_size { xsd:decimal }?& attribute scroll_wheel { bool }?& attribute show_edge_buttons { bool }?& attribute sprite_button_top { text }?& attribute sprite_button_top_pressed { text }?& attribute sprite_button_top_disabled { text }?& attribute sprite_button_top_over { text }?& attribute sprite_button_bottom { text }?& attribute sprite_button_bottom_pressed { text }?& attribute sprite_button_bottom_disabled { text }?& attribute sprite_button_bottom_over { text }?& attribute sprite_bar_vertical { text }?& attribute sprite_bar_vertical_over { text }?& attribute sprite_bar_vertical_pressed { text }?& attribute sprite_back_vertical { text }? } icon = element icon { attribute name { text }& attribute size { size }& attribute sprite { text }& attribute cell_id { text }? } tooltip = element tooltip { attribute name { text }& attribute sprite { text }?& attribute anchor { valign }?& attribute buffer_zone { xsd:decimal }?& attribute font { text }?& attribute maxwidth { xsd:decimal }?& attribute offset { pos }?& attribute textcolor { ccolor }?& attribute delay { xsd:integer }?& attribute use_object { text }?& attribute hide_object { bool }? } color = element color { rgba, attribute name { text } } ## # Sprites # ## sprites = element sprites { sprite* } sprite = element sprite { (effect?, image+), attribute name { text } } image = element image { effect?, ( attribute texture { text }?& attribute size { clientarea }?& attribute texture_size { clientarea }?& attribute real_texture_placement { rect }?& attribute cell_size { size }?& attribute backcolor { ccolor }?& attribute bordercolor { ccolor }?& attribute border { bool }?& attribute z_level { xsd:float }?& attribute fixed_h_aspect_ratio { xsd:decimal }?& attribute round_coordinates { bool }?& attribute wrap_mode { wrapmode }? ) } effect = element effect { attribute add_color { ccolor }?, attribute multiply_color { ccolor }?, attribute grayscale { empty }? } Index: ps/trunk/binaries/data/mods/mod/gui/gui.rng =================================================================== --- ps/trunk/binaries/data/mods/mod/gui/gui.rng (revision 19352) +++ ps/trunk/binaries/data/mods/mod/gui/gui.rng (revision 19353) @@ -1,852 +1,857 @@ true false left center right top center bottom repeat mirrored_repeat clamp_to_edge -?\d*\.?\d+%?([\+\-]\d*\.?\d+%?)* 0 255 0 255 0 255 0 255 [A-Za-z]+ 1 1 + + + + + Index: ps/trunk/source/gui/COList.cpp =================================================================== --- ps/trunk/source/gui/COList.cpp (revision 19352) +++ ps/trunk/source/gui/COList.cpp (revision 19353) @@ -1,462 +1,486 @@ /* Copyright (C) 2017 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * 0 A.D. is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with 0 A.D. If not, see . */ #include "precompiled.h" #include "COList.h" #include "i18n/L10n.h" #include "ps/CLogger.h" #include "soundmanager/ISoundManager.h" COList::COList() : CList() { AddSetting(GUIST_CGUISpriteInstance, "sprite_heading"); AddSetting(GUIST_float, "heading_height"); AddSetting(GUIST_bool, "sortable"); // The actual sorting is done in JS for more versatility AddSetting(GUIST_CStr, "selected_column"); AddSetting(GUIST_int, "selected_column_order"); AddSetting(GUIST_CGUISpriteInstance, "sprite_asc"); // Show the order of sorting AddSetting(GUIST_CGUISpriteInstance, "sprite_desc"); AddSetting(GUIST_CGUISpriteInstance, "sprite_not_sorted"); } void COList::SetupText() { if (!GetGUI()) return; CGUIList* pList; GUI::GetSettingPointer(this, "list", pList); m_ItemsYPositions.resize(pList->m_Items.size() + 1); // Delete all generated texts. Some could probably be saved, // but this is easier, and this function will never be called // continuously, or even often, so it'll probably be okay. for (SGUIText* const& t : m_GeneratedTexts) delete t; m_GeneratedTexts.clear(); CStrW font; if (GUI::GetSetting(this, "font", font) != PSRETURN_OK || font.empty()) // Use the default if none is specified // TODO Gee: (2004-08-14) Don't define standard like this. Do it with the default style. font = L"default"; bool scrollbar; GUI::GetSetting(this, "scrollbar", scrollbar); float width = GetListRect().GetWidth(); // remove scrollbar if applicable if (scrollbar && GetScrollBar(0).GetStyle()) width -= GetScrollBar(0).GetStyle()->m_Width; m_TotalAvailableColumnWidth = width; float buffer_zone = 0.f; GUI::GetSetting(this, "buffer_zone", buffer_zone); for (COListColumn column : m_Columns) { SGUIText* text = new SGUIText(); CGUIString gui_string; gui_string.SetValue(column.m_Heading); *text = GetGUI()->GenerateText(gui_string, font, width, buffer_zone, this); AddText(text); } // Generate texts float buffered_y = 0.f; for (size_t i = 0; i < pList->m_Items.size(); ++i) { m_ItemsYPositions[i] = buffered_y; float shift = 0.0f; for (size_t c = 0; c < m_Columns.size(); ++c) { CGUIList* pList_c; GUI::GetSettingPointer(this, "list_" + m_Columns[c].m_Id, pList_c); SGUIText* text = new SGUIText(); if (!pList_c->m_Items[i].GetOriginalString().empty()) *text = GetGUI()->GenerateText(pList_c->m_Items[i], font, width, buffer_zone, this); else { // Minimum height of a space character of the current font size CGUIString align_string; align_string.SetValue(L" "); *text = GetGUI()->GenerateText(align_string, font, width, buffer_zone, this); } shift = std::max(shift, text->m_Size.cy); AddText(text); } buffered_y += shift; } m_ItemsYPositions[pList->m_Items.size()] = buffered_y; if (scrollbar) { CRect rect = GetListRect(); GetScrollBar(0).SetScrollRange(m_ItemsYPositions.back()); GetScrollBar(0).SetScrollSpace(rect.GetHeight()); GetScrollBar(0).SetX(rect.right); GetScrollBar(0).SetY(rect.top); GetScrollBar(0).SetZ(GetBufferedZ()); GetScrollBar(0).SetLength(rect.bottom - rect.top); } } CRect COList::GetListRect() const { float headingHeight; GUI::GetSetting(this, "heading_height", headingHeight); return m_CachedActualSize + CRect(0, headingHeight, 0, 0); } void COList::HandleMessage(SGUIMessage& Message) { CList::HandleMessage(Message); switch (Message.type) { // If somebody clicks on the column heading case GUIM_MOUSE_PRESS_LEFT: { bool sortable; GUI::GetSetting(this, "sortable", sortable); if (!sortable) return; CPos mouse = GetMousePos(); if (!m_CachedActualSize.PointInside(mouse)) return; CStr selectedColumn; GUI::GetSetting(this, "selected_column", selectedColumn); int selectedColumnOrder; GUI::GetSetting(this, "selected_column_order", selectedColumnOrder); float headingHeight; GUI::GetSetting(this, "heading_height", headingHeight); float xpos = 0; for (COListColumn column : m_Columns) { + bool hidden = false; + GUI::GetSetting(this, "hidden_" + column.m_Id, hidden); + if (hidden) + continue; + float width = column.m_Width; // Check if it's a decimal value, and if so, assume relative positioning. if (column.m_Width < 1 && column.m_Width > 0) width *= m_TotalAvailableColumnWidth; CPos leftTopCorner = m_CachedActualSize.TopLeft() + CPos(xpos, 0); if (mouse.x >= leftTopCorner.x && mouse.x < leftTopCorner.x + width && mouse.y < leftTopCorner.y + headingHeight) { if (column.m_Id != selectedColumn) { selectedColumnOrder = 1; selectedColumn = column.m_Id; } else selectedColumnOrder = -selectedColumnOrder; GUI::SetSetting(this, "selected_column", column.m_Id); GUI::SetSetting(this, "selected_column_order", selectedColumnOrder); ScriptEvent("selectioncolumnchange"); CStrW soundPath; if (g_SoundManager && GUI::GetSetting(this, "sound_selected", soundPath) == PSRETURN_OK && !soundPath.empty()) g_SoundManager->PlayAsUI(soundPath.c_str(), false); return; } xpos += width; } return; } default: return; } } bool COList::HandleAdditionalChildren(const XMBElement& child, CXeromyces* pFile) { #define ELMT(x) int elmt_##x = pFile->GetElementID(#x) #define ATTR(x) int attr_##x = pFile->GetAttributeID(#x) ELMT(item); ELMT(column); ELMT(translatableAttribute); ATTR(id); ATTR(context); if (child.GetNodeName() == elmt_item) { AddItem(child.GetText().FromUTF8(), child.GetText().FromUTF8()); return true; } else if (child.GetNodeName() == elmt_column) { COListColumn column; + bool hidden = false; for (XMBAttribute attr : child.GetAttributes()) { CStr attr_name(pFile->GetAttributeString(attr.Name)); CStr attr_value(attr.Value); if (attr_name == "color") { CColor color; if (!GUI::ParseString(attr_value.FromUTF8(), color)) LOGERROR("GUI: Error parsing '%s' (\"%s\")", attr_name.c_str(), attr_value.c_str()); else column.m_TextColor = color; } else if (attr_name == "id") { column.m_Id = attr_value; } + else if (attr_name == "hidden") + { + if (!GUI::ParseString(attr_value.FromUTF8(), hidden)) + LOGERROR("GUI: Error parsing '%s' (\"%s\")", attr_name.c_str(), attr_value.c_str()); + } else if (attr_name == "width") { float width; if (!GUI::ParseString(attr_value.FromUTF8(), width)) LOGERROR("GUI: Error parsing '%s' (\"%s\")", attr_name.c_str(), attr_value.c_str()); else { // Check if it's a relative value, and save as decimal if so. if (attr_value.find("%") != std::string::npos) width = width / 100.f; column.m_Width = width; } } else if (attr_name == "heading") { column.m_Heading = attr_value.FromUTF8(); } } for (XMBElement grandchild : child.GetChildNodes()) { if (grandchild.GetNodeName() != elmt_translatableAttribute) continue; CStr attributeName(grandchild.GetAttributes().GetNamedItem(attr_id)); // only the heading is translatable for list column if (attributeName.empty() || attributeName != "heading") { LOGERROR("GUI: translatable attribute in olist column that isn't a heading. (object: %s)", this->GetPresentableName().c_str()); continue; } CStr value(grandchild.GetText()); if (value.empty()) continue; CStr context(grandchild.GetAttributes().GetNamedItem(attr_context)); // Read the context if any. if (!context.empty()) { CStr translatedValue(g_L10n.TranslateWithContext(context, value)); column.m_Heading = translatedValue.FromUTF8(); } else { CStr translatedValue(g_L10n.Translate(value)); column.m_Heading = translatedValue.FromUTF8(); } } m_Columns.push_back(column); AddSetting(GUIST_CGUIList, "list_" + column.m_Id); + AddSetting(GUIST_bool, "hidden_" + column.m_Id); + GUI::SetSetting(this, "hidden_" + column.m_Id, hidden); + SetupText(); return true; } else { return false; } } void COList::DrawList(const int& selected, const CStr& _sprite, const CStr& _sprite_selected, const CStr& _textcolor) { float bz = GetBufferedZ(); bool scrollbar; GUI::GetSetting(this, "scrollbar", scrollbar); if (scrollbar) IGUIScrollBarOwner::Draw(); if (!GetGUI()) return; CRect rect = GetListRect(); CGUISpriteInstance* sprite = NULL; CGUISpriteInstance* sprite_selectarea = NULL; int cell_id; GUI::GetSettingPointer(this, _sprite, sprite); GUI::GetSettingPointer(this, _sprite_selected, sprite_selectarea); GUI::GetSetting(this, "cell_id", cell_id); CGUIList* pList; GUI::GetSettingPointer(this, "list", pList); GetGUI()->DrawSprite(*sprite, cell_id, bz, rect); float scroll = 0.f; if (scrollbar) scroll = GetScrollBar(0).GetPos(); // Draw item selection if (selected != -1) { ENSURE(selected >= 0 && selected+1 < (int)m_ItemsYPositions.size()); // Get rectangle of selection: CRect rect_sel(rect.left, rect.top + m_ItemsYPositions[selected] - scroll, rect.right, rect.top + m_ItemsYPositions[selected+1] - scroll); if (rect_sel.top <= rect.bottom && rect_sel.bottom >= rect.top) { if (rect_sel.bottom > rect.bottom) rect_sel.bottom = rect.bottom; if (rect_sel.top < rect.top) rect_sel.top = rect.top; if (scrollbar) { // Remove any overlapping area of the scrollbar. if (rect_sel.right > GetScrollBar(0).GetOuterRect().left && rect_sel.right <= GetScrollBar(0).GetOuterRect().right) rect_sel.right = GetScrollBar(0).GetOuterRect().left; if (rect_sel.left >= GetScrollBar(0).GetOuterRect().left && rect_sel.left < GetScrollBar(0).GetOuterRect().right) rect_sel.left = GetScrollBar(0).GetOuterRect().right; } // Draw item selection GetGUI()->DrawSprite(*sprite_selectarea, cell_id, bz+0.05f, rect_sel); } } float headingHeight; GUI::GetSetting(this, "heading_height", headingHeight); // Draw line above column header CGUISpriteInstance* sprite_heading = NULL; GUI::GetSettingPointer(this, "sprite_heading", sprite_heading); CRect rect_head(m_CachedActualSize.left, m_CachedActualSize.top, m_CachedActualSize.right, m_CachedActualSize.top + headingHeight); GetGUI()->DrawSprite(*sprite_heading, cell_id, bz, rect_head); // Draw column headers bool sortable; GUI::GetSetting(this, "sortable", sortable); CStr selectedColumn; GUI::GetSetting(this, "selected_column", selectedColumn); int selectedColumnOrder; GUI::GetSetting(this, "selected_column_order", selectedColumnOrder); CColor color; GUI::GetSetting(this, _textcolor, color); float xpos = 0; for (size_t col = 0; col < m_Columns.size(); ++col) { + bool hidden = false; + GUI::GetSetting(this, "hidden_" + m_Columns[col].m_Id, hidden); + if (hidden) + continue; + // Check if it's a decimal value, and if so, assume relative positioning. float width = m_Columns[col].m_Width; if (m_Columns[col].m_Width < 1 && m_Columns[col].m_Width > 0) width *= m_TotalAvailableColumnWidth; CPos leftTopCorner = m_CachedActualSize.TopLeft() + CPos(xpos, 0); // Draw sort arrows in colum header if (sortable) { CGUISpriteInstance* sprite; if (selectedColumn == m_Columns[col].m_Id) { if (selectedColumnOrder == 0) LOGERROR("selected_column_order must not be 0"); if (selectedColumnOrder != -1) GUI::GetSettingPointer(this, "sprite_asc", sprite); else GUI::GetSettingPointer(this, "sprite_desc", sprite); } else GUI::GetSettingPointer(this, "sprite_not_sorted", sprite); GetGUI()->DrawSprite(*sprite, cell_id, bz + 0.1f, CRect(leftTopCorner + CPos(width - 16, 0), leftTopCorner + CPos(width, 16))); } // Draw column header text DrawText(col, color, leftTopCorner + CPos(0, 4), bz + 0.1f, rect_head); xpos += width; } // Draw list items for each column const size_t objectsCount = m_Columns.size(); for (size_t i = 0; i < pList->m_Items.size(); ++i) { if (m_ItemsYPositions[i+1] - scroll < 0 || m_ItemsYPositions[i] - scroll > rect.GetHeight()) continue; const float rowHeight = m_ItemsYPositions[i+1] - m_ItemsYPositions[i]; // Clipping area (we'll have to substract the scrollbar) CRect cliparea = GetListRect(); if (scrollbar) { if (cliparea.right > GetScrollBar(0).GetOuterRect().left && cliparea.right <= GetScrollBar(0).GetOuterRect().right) cliparea.right = GetScrollBar(0).GetOuterRect().left; if (cliparea.left >= GetScrollBar(0).GetOuterRect().left && cliparea.left < GetScrollBar(0).GetOuterRect().right) cliparea.left = GetScrollBar(0).GetOuterRect().right; } // Draw all items for that column xpos = 0; for (size_t col = 0; col < objectsCount; ++col) { + bool hidden = false; + GUI::GetSetting(this, "hidden_" + m_Columns[col].m_Id, hidden); + if (hidden) + continue; + // Determine text position and width const CPos textPos = rect.TopLeft() + CPos(xpos, -scroll + m_ItemsYPositions[i]); float width = m_Columns[col].m_Width; // Check if it's a decimal value, and if so, assume relative positioning. if (m_Columns[col].m_Width < 1 && m_Columns[col].m_Width > 0) width *= m_TotalAvailableColumnWidth; // Clip text to the column (to prevent drawing text into the neighboring column) CRect cliparea2 = cliparea; cliparea2.right = std::min(cliparea2.right, textPos.x + width); cliparea2.bottom = std::min(cliparea2.bottom, textPos.y + rowHeight); // Draw list item DrawText(objectsCount * (i +/*Heading*/1) + col, m_Columns[col].m_TextColor, textPos, bz + 0.1f, cliparea2); xpos += width; } } }