diff --git a/rsrc/dialogs/edit-party.xml b/rsrc/dialogs/edit-party.xml index a153d30b..d2ae1caf 100644 --- a/rsrc/dialogs/edit-party.xml +++ b/rsrc/dialogs/edit-party.xml @@ -1,58 +1,58 @@ + - - + + PC #1 + + + + - - PC #2 - - - - + + PC #2 + + + + - - PC #3 - - - - + + PC #3 + + + + - - PC #4 - - - - + + PC #4 + + + + - - PC #5 - - - - + + PC #5 + + + + - - PC #6 - - - - + + PC #6 + + + + - Click PC to rename. - diff --git a/rsrc/dialogs/preferences.xml b/rsrc/dialogs/preferences.xml index 4d82eebe..a37526bd 100644 --- a/rsrc/dialogs/preferences.xml +++ b/rsrc/dialogs/preferences.xml @@ -1,46 +1,45 @@ - - - Blades of Exile Preferences: + + Blades of Exile Preferences - Display alignment: - + Display alignment: + - Top Left - Top Right - Center - Bottom Left - Bottom Right - Small Window (not full screen) + Top Left + Top Right + Center + Bottom Left + Bottom Right + Small Window (not full screen) - Game speed: + Game speed: - Fast - Medium - Slow - Quite Slow + Fast + Medium + Slow + Quite Slow - For older machines: - No graphics frills (lose special effects) - Turn off terrain animation - Turn of frills on shore - Don't Save Maps - No Sounds - Show room descriptions more than once - Never show instant help - + For older machines: + + No graphics frills (lose special effects) + + Turn off terrain animation + Turn of frills on shore + Don't Save Maps + No Sounds + Show room descriptions more than once + Never show instant help + Reset instant help (all help windows will reappear) - Make game easier (monsters much weaker) - Fewer wandering monsters - Skip splash screen on startup - Apply UI scaling + Make game easier (monsters much weaker) + Fewer wandering monsters + Skip splash screen on startup + Apply UI scaling - - + + diff --git a/rsrc/schemas/dialog.xsd b/rsrc/schemas/dialog.xsd index 51574bbc..58f291fb 100644 --- a/rsrc/schemas/dialog.xsd +++ b/rsrc/schemas/dialog.xsd @@ -71,6 +71,25 @@ + + + + + + + + + + + + + + + + + + + @@ -81,6 +100,11 @@ + + + + + @@ -136,6 +160,7 @@ + @@ -157,6 +182,7 @@ + @@ -170,6 +196,7 @@ + @@ -185,6 +212,7 @@ + @@ -199,6 +227,7 @@ + @@ -236,6 +265,7 @@ + @@ -278,5 +308,9 @@ + + + + diff --git a/src/dialogxml/dialogs/dialog.cpp b/src/dialogxml/dialogs/dialog.cpp index e14cc19a..3d7922dc 100644 --- a/src/dialogxml/dialogs/dialog.cpp +++ b/src/dialogxml/dialogs/dialog.cpp @@ -216,44 +216,133 @@ void cDialog::loadFromFile(std::string path){ } vector specificTabs, reverseTabs; + std::pair prevCtrl{"", nullptr}; for(node = node.begin(xml.FirstChildElement()); node != node.end(); node++){ node->GetValue(&type); + ctrlIter inserted; // Yes, I'm using insert instead of [] to add elements to the map. // In this situation, it's actually easier that way; the reason being, the // map key is obtained from the name attribute of each element. if(type == "field") { auto field = parse(*node); - controls.insert(field); + inserted = controls.insert(field).first; tabOrder.push_back(field); if(field.second->tabOrder > 0) specificTabs.push_back(field.second->tabOrder); else if(field.second->tabOrder < 0) reverseTabs.push_back(field.second->tabOrder); } else if(type == "text") - controls.insert(parse(*node)); + inserted = controls.insert(parse(*node)).first; else if(type == "pict") - controls.insert(parse(*node)); + inserted = controls.insert(parse(*node)).first; else if(type == "slider") - controls.insert(parse(*node)); + inserted = controls.insert(parse(*node)).first; else if(type == "button") - controls.insert(parse(*node)); + inserted = controls.insert(parse(*node)).first; else if(type == "led") - controls.insert(parse(*node)); + inserted = controls.insert(parse(*node)).first; else if(type == "group") - controls.insert(parse(*node)); + inserted = controls.insert(parse(*node)).first; else if(type == "stack") { auto parsed = parse(*node); - controls.insert(parsed); + inserted = controls.insert(parsed).first; // Now, if it contains any fields, their tab order must be accounted for parsed.second->fillTabOrder(specificTabs, reverseTabs); } else if(type == "pane") { auto parsed = parse(*node); - controls.insert(parsed); + inserted = controls.insert(parsed).first; // TODO: Now, if it contains any fields, their tab order must be accounted for //parsed.second->fillTabOrder(specificTabs, reverseTabs); } else throw xBadNode(type,node->Row(),node->Column(),fname); + if(prevCtrl.second) { + if(inserted->second->anchor == "$$prev$$" && prevCtrl.second->anchor == "$$next$$") { + throw xBadVal(type, "anchor", "", node->Row(), node->Column(), fname); + } else if(inserted->second->anchor == "$$prev$$") { + inserted->second->anchor = prevCtrl.first; + } else if(prevCtrl.second->anchor == "$$next$$") { + prevCtrl.second->anchor = inserted->first; + } + } + prevCtrl = *inserted; } + // Resolve relative positioning + bool all_resolved = true; + do { + all_resolved = true; + for(auto& p : controls) { + auto ctrl = p.second; + if(!ctrl->anchor.empty()) { + auto anchor = controls[ctrl->anchor]; + if(!anchor->anchor.empty()) { + // Make sure it's not a loop! + std::vector refs{ctrl->anchor}; + while(!anchor->anchor.empty()) { + refs.push_back(anchor->anchor); + anchor = controls[anchor->anchor]; + if(std::find(refs.begin(), refs.end(), anchor->anchor) != refs.end()) { + std::string ctrlType; + switch(ctrl->getType()) { + case CTRL_UNKNOWN: ctrlType = "???"; break; + case CTRL_BTN: ctrlType = "button"; break; + case CTRL_LED: ctrlType = "led"; break; + case CTRL_PICT: ctrlType = "pict"; break; + case CTRL_FIELD: ctrlType = "field"; break; + case CTRL_TEXT: ctrlType = "text"; break; + case CTRL_GROUP: ctrlType = "group"; break; + case CTRL_STACK: ctrlType = "stack"; break; + case CTRL_SCROLL: ctrlType = "slider"; break; + case CTRL_PANE: ctrlType = "pane"; break; + } + throw xBadVal(ctrlType, "anchor", "", 0, 0, fname); + } + } + all_resolved = false; + continue; + } + ctrl->relocateRelative(ctrl->frame.topLeft(), anchor, ctrl->horz, ctrl->vert); + ctrl->anchor.clear(); + ctrl->horz = ctrl->vert = POS_ABS; + } else if(auto pane = dynamic_cast(ctrl)) { + pane->forEach([this, &all_resolved](const std::string&, cControl& ctrl) { + // TODO: Deduplicate this code (it's functionally identical to the above non-container code) + if(!ctrl.anchor.empty()) { + auto anchor = controls[ctrl.anchor]; + if(!anchor->anchor.empty()) { + // Make sure it's not a loop! + std::vector refs{ctrl.anchor}; + while(!anchor->anchor.empty()) { + refs.push_back(anchor->anchor); + anchor = controls[anchor->anchor]; + if(std::find(refs.begin(), refs.end(), anchor->anchor) != refs.end()) { + std::string ctrlType; + switch(ctrl.getType()) { + case CTRL_UNKNOWN: ctrlType = "???"; break; + case CTRL_BTN: ctrlType = "button"; break; + case CTRL_LED: ctrlType = "led"; break; + case CTRL_PICT: ctrlType = "pict"; break; + case CTRL_FIELD: ctrlType = "field"; break; + case CTRL_TEXT: ctrlType = "text"; break; + case CTRL_GROUP: ctrlType = "group"; break; + case CTRL_STACK: ctrlType = "stack"; break; + case CTRL_SCROLL: ctrlType = "slider"; break; + case CTRL_PANE: ctrlType = "pane"; break; + } + throw xBadVal(ctrlType, "anchor", "", 0, 0, fname); + } + } + all_resolved = false; + return; + } + ctrl.relocateRelative(ctrl.frame.topLeft(), anchor, ctrl.horz, ctrl.vert); + ctrl.anchor.clear(); + ctrl.horz = ctrl.vert = POS_ABS; + } + }); + } + } + } while(!all_resolved); + // Set the default button. if(hasControl(defaultButton)) getControl(defaultButton).attachKey(enterKey); @@ -317,29 +406,43 @@ void cDialog::loadFromFile(std::string path){ // now calculate window rect winRect = rectangle(); recalcRect(); - ctrlIter iter = controls.begin(); currentFocus = ""; - while(iter != controls.end()){ + for(ctrlIter iter = controls.begin(); iter != controls.end(); iter++){ if(typeid(iter->second) == typeid(cTextField*)){ if(currentFocus.empty()) currentFocus = iter->first; break; } - iter++; } } void cDialog::recalcRect(){ - ctrlIter iter = controls.begin(); - while(iter != controls.end()){ + bool haveRel = false; + for(ctrlIter iter = controls.begin(); iter != controls.end(); iter++) { + using namespace std::placeholders; + if(auto container = dynamic_cast(iter->second)) + container->forEach(std::bind(&cControl::recalcRect, _2)); + iter->second->recalcRect(); rectangle frame = iter->second->getBounds(); - if(frame.right > winRect.right) + haveRel = haveRel || iter->second->horz != POS_ABS || iter->second->vert != POS_ABS; + if(iter->second->horz != POS_REL_NEG && frame.right > winRect.right) winRect.right = frame.right; - if(frame.bottom > winRect.bottom) + if(iter->second->vert != POS_REL_NEG && frame.bottom > winRect.bottom) winRect.bottom = frame.bottom; - iter++; } winRect.right += 6; winRect.bottom += 6; + if(!haveRel) return; + // Resolve any remaining relative positions + // Controls placed relative to the dialog's edges can go off the edge of the dialog + for(ctrlIter iter = controls.begin(); iter != controls.end(); iter++) { + location pos = iter->second->getBounds().topLeft(); + if(iter->second->horz == POS_REL_NEG) + pos.x = winRect.right - pos.x; + if(iter->second->vert == POS_REL_NEG) + pos.y = winRect.bottom - pos.y; + iter->second->horz = iter->second->vert = POS_ABS; + iter->second->relocate(pos); + } } void cDialog::init(){ diff --git a/src/dialogxml/widgets/button.cpp b/src/dialogxml/widgets/button.cpp index 0a2ec6d3..b60d5c7a 100644 --- a/src/dialogxml/widgets/button.cpp +++ b/src/dialogxml/widgets/button.cpp @@ -160,11 +160,12 @@ bool cButton::parseContent(ticpp::Node& content, int n, std::string tagName, std } return cControl::parseContent(content, n, tagName, fname, text); } + +static const std::set labelledButtons{BTN_TINY, BTN_LED, BTN_PUSH}; void cButton::validatePostParse(ticpp::Element& elem, std::string fname, const std::set& attrs, const std::multiset& elems) { cControl::validatePostParse(elem, fname, attrs, elems); if(getType() == CTRL_BTN && !attrs.count("type")) throw xMissingAttr(elem.Value(), "type", elem.Row(), elem.Column(), fname); - static const std::set labelledButtons{BTN_TINY, BTN_LED, BTN_PUSH}; if(labelledButtons.count(type)) { if(!attrs.count("color") && !attrs.count("colour") && parent->getBg() == cDialog::BG_DARK) setColour(sf::Color::White); @@ -177,6 +178,17 @@ location cButton::getPreferredSize() { return {btnRects[type][0].width(), btnRects[type][0].height()}; } +void cButton::recalcRect() { + location bestSz = getPreferredSize(); + if(labelledButtons.count(type)) { + if(frame.width() < bestSz.x) frame.width() = bestSz.x; + if(frame.height() < bestSz.y) frame.height() = bestSz.y; + } else { + frame.width() = bestSz.x; + frame.height() = bestSz.y; + } +} + // Indices within the buttons array. size_t cButton::btnGW[14] = { 0, // BTN_SM @@ -404,6 +416,10 @@ bool cLed::parseContent(ticpp::Node& content, int n, std::string tagName, std::s return cButton::parseContent(content, n, tagName, fname, text); } +location cLed::getPreferredSize() { + return {ledRects[0][0].width(), ledRects[0][0].height()}; +} + void cLedGroup::addChoice(cLed* ctrl, std::string key) { choices[key] = ctrl; if(ctrl->getState() != led_off) @@ -649,6 +665,5 @@ bool cLedGroup::parseContent(ticpp::Node& content, int n, std::string tagName, s void cLedGroup::validatePostParse(ticpp::Element& who, std::string fname, const std::set& attrs, const std::multiset& nodes) { // Don't defer to super-class; groups are an abstract container that doesn't require a position. //cControl::validatePostParse(who, fname, attrs, nodes); - recalcRect(); frameStyle = FRM_NONE; } diff --git a/src/dialogxml/widgets/button.hpp b/src/dialogxml/widgets/button.hpp index 7f6cddae..7f73e317 100644 --- a/src/dialogxml/widgets/button.hpp +++ b/src/dialogxml/widgets/button.hpp @@ -54,6 +54,7 @@ public: bool parseContent(ticpp::Node& content, int n, std::string tagName, std::string fname, std::string& text) override; void validatePostParse(ticpp::Element& elem, std::string fname, const std::set& attrs, const std::multiset& elems) override; location getPreferredSize() override; + void recalcRect() override; /// Set the type of this button. /// @param newType The desired button type. void setBtnType(eBtnType newType); @@ -115,6 +116,7 @@ public: static bool noAction(cDialog&,std::string,eKeyMod) {return true;} bool parseAttribute(ticpp::Attribute& attr, std::string tagName, std::string fname) override; bool parseContent(ticpp::Node& content, int n, std::string tagName, std::string fname, std::string& text) override; + location getPreferredSize() override; storage_t store() override; void restore(storage_t to) override; /// Create a new LED button. @@ -239,7 +241,7 @@ public: /// Recalculate the LED group's bounding rect. /// Call this after adding choices to the group to ensure that the choice is within the bounding rect. /// If a choice is not within the bounding rect, it will not respond to clicks. - void recalcRect(); + void recalcRect() override; void forEach(std::function callback) override; /// A convenience type for making an iterator into the choice map. typedef std::map::iterator ledIter; diff --git a/src/dialogxml/widgets/control.cpp b/src/dialogxml/widgets/control.cpp index 68b32e52..318a2d82 100644 --- a/src/dialogxml/widgets/control.cpp +++ b/src/dialogxml/widgets/control.cpp @@ -37,6 +37,29 @@ void cControl::relocate(location to) { frame.offset(to.x - frame.left, to.y - frame.top); } +void cControl::relocateRelative(location to, cControl* anchor, ePosition h, ePosition v) { + if(anchor == nullptr) anchor = this; + // Determine the anchor point of the relocation + location anchorPoint; + switch(h) { + case POS_ABS: anchorPoint.x = 0; break; + case POS_REL_PLUS: anchorPoint.x = anchor->frame.right; break; + case POS_REL_NEG: anchorPoint.x = anchor->frame.left; to.x = -to.x; break; + case POS_CONT_PLUS: anchorPoint.x = anchor->frame.left; break; + case POS_CONT_NEG: anchorPoint.x = anchor->frame.right; to.x = -to.x; break; + } + switch(v) { + case POS_ABS: anchorPoint.y = 0; break; + case POS_REL_PLUS: anchorPoint.y = anchor->frame.bottom; break; + case POS_REL_NEG: anchorPoint.y = anchor->frame.top; to.y = -to.y; break; + case POS_CONT_PLUS: anchorPoint.y = anchor->frame.top; break; + case POS_CONT_NEG: anchorPoint.y = anchor->frame.bottom; to.y = -to.y; break; + } + to.x += anchorPoint.x; + to.y += anchorPoint.y; + relocate(to); +} + const char* xHandlerNotSupported::msg[4] = { "This control cannot handle click events.\n", "This control cannot handle focus events.\n", @@ -491,6 +514,51 @@ std::string cControl::parse(ticpp::Element& who, std::string fname) { bool cControl::parseAttribute(ticpp::Attribute& attr, std::string tagName, std::string fname) { std::string name; attr.GetName(&name); + // Relative positioning + if(name == "relative") { + static auto space = " \t"; + std::string rel = attr.Value(); + const xBadVal err(tagName, name, rel, attr.Row(), attr.Column(), fname); + size_t border = rel.find_first_of(space); + if(border != std::string::npos) { + size_t border_end = rel.find_last_of(space); + // Error if any of [border, border_end] are not spaces + for(size_t i = border + 1; i < border_end; i++) { + if(rel[i] != ' ' && rel[i] != '\t') throw err; + } + std::string h = rel.substr(0, border), v = rel.substr(border_end + 1); + if(h == "abs") horz = POS_ABS; + else if(h == "pos") horz = POS_REL_PLUS; + else if(h == "neg") horz = POS_REL_NEG; + else if(h == "pos-in") horz = POS_CONT_PLUS; + else if(h == "neg-in") horz = POS_CONT_NEG; + else throw err; + if(v == "abs") vert = POS_ABS; + else if(v == "pos") vert = POS_REL_PLUS; + else if(v == "neg") vert = POS_REL_NEG; + else if(v == "pos-in") vert = POS_CONT_PLUS; + else if(v == "neg-in") vert = POS_CONT_NEG; + else throw err; + } + else if(rel == "abs") horz = vert = POS_ABS; + else if(rel == "pos") horz = vert = POS_REL_PLUS; + else if(rel == "neg") horz = vert = POS_REL_NEG; + else if(rel == "pos-in") horz = vert = POS_CONT_PLUS; + else if(rel == "neg-in") horz = vert = POS_CONT_NEG; + else throw err; + return true; + } + if(name == "anchor") { + anchor = attr.Value(); + return true; + } + if(name == "rel-anchor") { + std::string val = attr.Value(); + if(val == "next") anchor = "$$next$$"; + else if(val == "prev") anchor = "$$prev$$"; + else throw xBadVal(tagName, name, val, attr.Row(), attr.Column(), fname); + return true; + } // Colour and formatting, if supported if(name == "framed" && canFormat(TXT_FRAME)) { std::string val; @@ -559,6 +627,13 @@ bool cControl::parseContent(ticpp::Node&, int, std::string, std::string, std::st void cControl::validatePostParse(ticpp::Element& elem, std::string fname, const std::set& attrs, const std::multiset&) { if(!attrs.count("left")) throw xMissingAttr(elem.Value(), "left", elem.Row(), elem.Column(), fname); if(!attrs.count("top")) throw xMissingAttr(elem.Value(), "top", elem.Row(), elem.Column(), fname); + if(attrs.count("relative") && !attrs.count("anchor") && !attrs.count("rel-anchor")) { + // If relative is specified, an anchor is required... unless it's abs or neg + if((horz != POS_ABS && horz != POS_REL_NEG) || (vert != POS_ABS && vert != POS_REL_NEG)) + throw xMissingAttr(elem.Value(), "anchor", elem.Row(), elem.Column(), fname); + } + if(attrs.count("anchor") && attrs.count("rel-anchor")) + throw xBadAttr(elem.Value(), "(rel-)anchor", elem.Row(), elem.Column(), fname); } cControl::~cControl() {} diff --git a/src/dialogxml/widgets/control.hpp b/src/dialogxml/widgets/control.hpp index bf9a3e9b..17dee876 100644 --- a/src/dialogxml/widgets/control.hpp +++ b/src/dialogxml/widgets/control.hpp @@ -55,6 +55,14 @@ enum eControlType { CTRL_PANE, ///< A scroll pane }; +enum ePosition { + POS_ABS, ///< Absolute positioning (possibly relative to a container) + POS_REL_PLUS, ///< Positioned relative to another widget, measuring down from its bottom edge or right from its right edge + POS_REL_NEG, ///< Positioned relative to another widget, measuring up from its top edge or left from its left edge + POS_CONT_PLUS, ///< Positioned relative to another widget, measuring down from its top edge or right from its left edge + POS_CONT_NEG, ///< Positioned relative to another widget, measuering up from its bottom edge or left from its right edge +}; + /// Thrown when you try to set a handler that the control does not support. class xHandlerNotSupported : public std::exception { static const char* msg[4]; @@ -251,6 +259,13 @@ public: /// Set the position of this control. /// @param to The new position. void relocate(location to); + /// Set the position of this control relative to another control. + /// @param to The new relative position. + /// @param anchor The position will be calculated relative to this control's position. + /// If nullptr, the control will be moved relative to its current position. + /// @param horz How to place the control on the horizontal axis. + /// @param vert How to place the control on the vertical axis. + void relocateRelative(location to, cControl* anchor, ePosition horz, ePosition vert); /// Get the control's text as an integer. /// @return The control's text, coerced to an integer. long long getTextAsNum(); @@ -330,6 +345,10 @@ public: cControl& operator=(cControl& other) = delete; cControl(cControl& other) = delete; protected: + /// If the control automatically determines its rect based on certain criteria, override this. + /// It will automatically be called during parsing. + /// When overridden, it should normally be public. + virtual void recalcRect() {} /// Returns a list of event handlers that this control supports. /// @return The list of handlers as a std::set. /// @@ -426,6 +445,9 @@ private: friend class cDialog; // TODO: This is only so it can access parseColour... hack! eControlType type; std::map event_handlers; + // Transient values only used during parsing + ePosition horz = POS_ABS, vert = POS_ABS; + std::string anchor; }; /// A superclass to represent a control that contains other controls. diff --git a/src/dialogxml/widgets/pict.hpp b/src/dialogxml/widgets/pict.hpp index 377ca631..5843b9c8 100644 --- a/src/dialogxml/widgets/pict.hpp +++ b/src/dialogxml/widgets/pict.hpp @@ -47,7 +47,7 @@ public: /// @param num The new icon index. void setPict(pic_num_t num); /// Automatically recalculate the icon's bounding rect based on its current picture. - void recalcRect(); + void recalcRect() override; /// Get the current icon. /// @return The number of the current icon. pic_num_t getPicNum(); diff --git a/src/dialogxml/widgets/scrollpane.cpp b/src/dialogxml/widgets/scrollpane.cpp index 10e483f4..f16aaa49 100644 --- a/src/dialogxml/widgets/scrollpane.cpp +++ b/src/dialogxml/widgets/scrollpane.cpp @@ -219,5 +219,4 @@ bool cScrollPane::parseContent(ticpp::Node& content, int n, std::string tagName, void cScrollPane::validatePostParse(ticpp::Element& who, std::string fname, const std::set& attrs, const std::multiset& nodes) { cContainer::validatePostParse(who, fname, attrs, nodes); if(!attrs.count("style")) setStyle(SCROLL_LED); - recalcRect(); } diff --git a/src/dialogxml/widgets/scrollpane.hpp b/src/dialogxml/widgets/scrollpane.hpp index 187783b4..9c834ca0 100644 --- a/src/dialogxml/widgets/scrollpane.hpp +++ b/src/dialogxml/widgets/scrollpane.hpp @@ -41,7 +41,7 @@ public: /// @note This function is intended for internal use, which is why it takes a control pointer instead of a unique key. void addChild(cControl* ctrl, std::string key); /// Recalculate the pane's bounding rect based on its contained controls. - void recalcRect(); + void recalcRect() override; /// Get the pane's current scroll position. /// @return The current position. long getPosition(); diff --git a/src/dialogxml/widgets/stack.cpp b/src/dialogxml/widgets/stack.cpp index bf1af069..15a91879 100644 --- a/src/dialogxml/widgets/stack.cpp +++ b/src/dialogxml/widgets/stack.cpp @@ -201,6 +201,5 @@ bool cStack::parseContent(ticpp::Node& content, int n, std::string tagName, std: void cStack::validatePostParse(ticpp::Element& who, std::string fname, const std::set& attrs, const std::multiset& nodes) { validatePostParse(who, fname, attrs, nodes); - recalcRect(); } diff --git a/src/dialogxml/widgets/stack.hpp b/src/dialogxml/widgets/stack.hpp index 10dbf537..3b8b31fe 100644 --- a/src/dialogxml/widgets/stack.hpp +++ b/src/dialogxml/widgets/stack.hpp @@ -68,7 +68,7 @@ public: /// @return The number of pages size_t getPageCount(); /// Recalculate the stack's bounding rect based on its contained controls. - void recalcRect(); + void recalcRect() override; /// Adds any fields in this stack to the tab order building arrays. /// Meant for internal use. void fillTabOrder(std::vector& specificTabs, std::vector& reverseTabs); diff --git a/src/doxy/mainpage.md b/src/doxy/mainpage.md index 3c03651d..759c1a0b 100644 --- a/src/doxy/mainpage.md +++ b/src/doxy/mainpage.md @@ -27,7 +27,25 @@ The following attributes are allowed on all or most elements: rect of the control within the dialog. All non-container controls support these attributes, and in fact the `top` and `left` attributes are required. Some controls may ignore the `width` and `height` -attributes. +attributes. All of these must be non-negative integers. +* `relative` - Specifies how the location is computed; defaults to `"abs"`. +Must be one or two (space-separated) of the following; +if two are specified, they represent the mode used for calculating x and y respectively: + * `abs` - Computed in global dialog space, relative to the top left corner. + * `pos` - Computed relative to the reference widget's bottom right corner. + * `pos-in` - Computed relative to the reference widget's top left corner. + * `neg` - Computed relative to the reference widget's top left corner, +with the axes inverted (as if top and left were negative). +As a special case, if this is used with no reference widget, +the widget's size does not contribute to computation of the dialog's size, +and the widget is positioned relative to the dialog's bottom right corner. + * `neg-in` - Computed relative to the reference widget's bottom right corner, +with the axes inverted. +* `anchor` - Specifies the `name` of the reference widget for this widget's location. +* `rel-anchor` - Set to `prev` or `next` to use the previous or next element in the XML ordering +as the reference widget for this widget's location. +This currently does not work for widgets in containers. +Mutually exclusive with `anchor`. * `def-key` - Specifies the default keyboard shortcut for the control. See **Keyboard Shortcuts** below for more information on the format of this attribute.