dialogs can have a button respond to Escape

I'm planning to make it so the Enter key will never cancel a yes/no dialog. To do this, I'm adding an Escape button to dialogs. So cancel/accept keyboard shortcuts will be predictable and intuitive. Dialogs that require extra confirmation will have a 'really confirm' LED.
This commit is contained in:
2025-03-27 13:26:36 -05:00
parent 3ab3171287
commit 1b65be5ab4
6 changed files with 50 additions and 19 deletions

View File

@@ -1,6 +1,6 @@
<?xml version='1.0' encoding='UTF-8' standalone='no'?> <?xml version='1.0' encoding='UTF-8' standalone='no'?>
<?xml-stylesheet href="dialog.xsl" type="text/xsl"?> <?xml-stylesheet href="dialog.xsl" type="text/xsl"?>
<dialog defbtn='okay'> <dialog defbtn='okay' escbtn='okay'>
<pict type='dlog' num='16' top='8' left='8'/> <pict type='dlog' num='16' top='8' left='8'/>
<text top='8' left='50' width='400' height='16'>DEBUG MODE HELP</text> <text top='8' left='50' width='400' height='16'>DEBUG MODE HELP</text>
<text top='22' left='50' width='400' height='64'> <text top='22' left='50' width='400' height='64'>

View File

@@ -398,6 +398,7 @@
<xs:attribute name="debug" default="false" type="bool"/> <xs:attribute name="debug" default="false" type="bool"/>
<xs:attribute name="fore" default="black"/> <xs:attribute name="fore" default="black"/>
<xs:attribute name="defbtn" type="xs:token"/> <xs:attribute name="defbtn" type="xs:token"/>
<xs:attribute name="escbtn" type="xs:token"/>
</xs:complexType> </xs:complexType>
<xs:unique name="FieldTabOrder"> <xs:unique name="FieldTabOrder">
<xs:selector xpath="*" /> <xs:selector xpath="*" />
@@ -411,6 +412,10 @@
<xs:selector xpath="."/> <xs:selector xpath="."/>
<xs:field xpath="@defbtn"/> <xs:field xpath="@defbtn"/>
</xs:keyref> </xs:keyref>
<xs:keyref name="escapeButton" refer="uniqueID">
<xs:selector xpath="."/>
<xs:field xpath="@escbtn"/>
</xs:keyref>
<xs:keyref name="sliderLink" refer="uniqueID"> <xs:keyref name="sliderLink" refer="uniqueID">
<xs:selector xpath="slider"/> <xs:selector xpath="slider"/>
<xs:field xpath="@link"/> <xs:field xpath="@link"/>

View File

@@ -158,7 +158,7 @@ void cDialog::loadFromFile(const DialogDefn& file){
Iterator<Attribute> attr; Iterator<Attribute> attr;
Iterator<Element> node; Iterator<Element> node;
string type, name, val, defbtn; string type, name, val, defbtn, escbtn;
xml.FirstChildElement()->GetValue(&type); xml.FirstChildElement()->GetValue(&type);
if(type != "dialog") throw xBadNode(type,xml.FirstChildElement()->Row(),xml.FirstChildElement()->Column(),fname); if(type != "dialog") throw xBadNode(type,xml.FirstChildElement()->Row(),xml.FirstChildElement()->Column(),fname);
@@ -181,8 +181,10 @@ void cDialog::loadFromFile(const DialogDefn& file){
throw xBadVal("text",name,val,attr->Row(),attr->Column(),fname); throw xBadVal("text",name,val,attr->Row(),attr->Column(),fname);
} }
defTextClr = clr; defTextClr = clr;
} else if(name == "defbtn") { }else if(name == "defbtn"){
defbtn = val; defbtn = val;
}else if(name == "escbtn"){
escbtn = val;
}else if(name != "debug") }else if(name != "debug")
throw xBadAttr(type,name,attr->Row(),attr->Column(),fname); throw xBadAttr(type,name,attr->Row(),attr->Column(),fname);
} }
@@ -337,6 +339,8 @@ void cDialog::loadFromFile(const DialogDefn& file){
// Set the default button. // Set the default button.
setDefaultButton(defbtn); setDefaultButton(defbtn);
// Set the escape button.
setEscapeButton(escbtn);
// Sort by tab order // Sort by tab order
// First, fill any gaps that might have been left, using ones that had no specific tab order // First, fill any gaps that might have been left, using ones that had no specific tab order
@@ -912,11 +916,12 @@ bool cDialog::addLabelFor(std::string key, std::string label, eLabelPos where, s
void cDialog::process_keystroke(cKey keyHit){ void cDialog::process_keystroke(cKey keyHit){
ctrlIter iter = controls.begin(); ctrlIter iter = controls.begin();
bool enterKeyHit = keyHit.spec && keyHit.k == key_enter; bool enterKeyHit = keyHit.spec && keyHit.k == key_enter;
bool escapeKeyHit = keyHit.spec && keyHit.k == key_esc;
while(iter != controls.end()){ while(iter != controls.end()){
cControl* ctrl = iter->second; cControl* ctrl = iter->second;
if(ctrl->isVisible()){ if(ctrl->isVisible()){
if(ctrl->isClickable() && if(ctrl->isClickable() &&
(ctrl->getAttachedKey() == keyHit || (ctrl->isDefault() && enterKeyHit))){ (ctrl->getAttachedKey() == keyHit || (ctrl->isDefault() && enterKeyHit) || (ctrl->isEscape() && escapeKeyHit))){
ctrl->handleKeyTriggered(*this); ctrl->handleKeyTriggered(*this);
return; return;
@@ -940,11 +945,6 @@ void cDialog::process_keystroke(cKey keyHit){
} }
iter++; iter++;
} }
// If you get an escape and it isn't processed, make it an enter.
if(keyHit.spec && keyHit.k == key_esc){
keyHit.k = key_enter;
process_keystroke(keyHit);
}
} }
void cDialog::process_click(location where, eKeyMod mods, cFramerateLimiter& fps_limiter){ void cDialog::process_click(location where, eKeyMod mods, cFramerateLimiter& fps_limiter){
@@ -1157,21 +1157,38 @@ bool cDialog::hasControl(std::string id) const {
return false; return false;
} }
void cDialog::setDefaultButton(std::string defbtn) { void cDialog::setSpecialButton(std::string& name_ref, std::string name, bool escape) {
if(!defbtn.empty() && !hasControl(defbtn)){ if(!name.empty() && !hasControl(name)){
// this is likely because the dialogxml is malformed. maybe the linter already checks this, // this is likely because the dialogxml is malformed. maybe the linter already checks this,
// but the engine might as well also. // but the engine might as well also.
throw std::string { "Requested default button does not exist: " } + defbtn; throw std::string { "Requested button does not exist: " } + name;
} }
if(!defaultButton.empty()){ if(!name_ref.empty()){
getControl(defaultButton).setDefault(false); if(escape){
getControl(name_ref).setEscape(false);
}else{
getControl(name_ref).setDefault(false);
}
name_ref = "";
} }
if(!defbtn.empty()){ if(!name.empty()){
defaultButton = defbtn; name_ref = name;
getControl(defaultButton).setDefault(true); if(escape){
getControl(name).setEscape(true);
}else{
getControl(name).setDefault(true);
}
} }
} }
void cDialog::setDefaultButton(std::string defbtn) {
setSpecialButton(defaultButton, defbtn, false);
}
void cDialog::setEscapeButton(std::string escbtn) {
setSpecialButton(escapeButton, escbtn, true);
}
const char*const xBadVal::CONTENT = "$content$"; const char*const xBadVal::CONTENT = "$content$";
cDialogIterator::cDialogIterator() : parent(nullptr) {} cDialogIterator::cDialogIterator() : parent(nullptr) {}

View File

@@ -266,7 +266,9 @@ public:
inline void setAnimPictFPS(int fps) { if(fps == -1) fps = 2; anim_pict_fps = fps; } inline void setAnimPictFPS(int fps) { if(fps == -1) fps = 2; anim_pict_fps = fps; }
inline void setDoAnimations(bool value) { doAnimations = value; } inline void setDoAnimations(bool value) { doAnimations = value; }
void setDefaultButton(std::string defbtn); void setDefaultButton(std::string defbtn);
void setEscapeButton(std::string escbtn);
private: private:
void setSpecialButton(std::string& name_ref, std::string name, bool escape);
void draw(); void draw();
void handle_events(); void handle_events();
void handle_one_event(const sf::Event&, cFramerateLimiter& fps_limiter); void handle_one_event(const sf::Event&, cFramerateLimiter& fps_limiter);
@@ -277,6 +279,7 @@ private:
boost::any result; boost::any result;
std::string fname; std::string fname;
std::string defaultButton; std::string defaultButton;
std::string escapeButton;
sf::Clock animTimer, paintTimer; sf::Clock animTimer, paintTimer;
friend class cControl; friend class cControl;
friend class cContainer; friend class cContainer;

View File

@@ -172,6 +172,8 @@ public:
std::string getAttachedKeyDescription() const; std::string getAttachedKeyDescription() const;
inline void setDefault(bool value) { isDefaultControl = value; } inline void setDefault(bool value) { isDefaultControl = value; }
inline bool isDefault() { return isDefaultControl; } inline bool isDefault() { return isDefaultControl; }
inline void setEscape(bool value) { isEscapeControl = value; }
inline bool isEscape() { return isEscapeControl; }
/// Attach an event handler to this control. /// Attach an event handler to this control.
/// @tparam t The type of event to attach. /// @tparam t The type of event to attach.
/// @param handler The event handler function or functor. Its signature depends on the event type. /// @param handler The event handler function or functor. Its signature depends on the event type.
@@ -477,6 +479,8 @@ protected:
cKey key; cKey key;
/// Whether the control is the default control of its dialog. /// Whether the control is the default control of its dialog.
bool isDefaultControl = false; bool isDefaultControl = false;
/// Whether the control is the cancel control of its dialog.
bool isEscapeControl = false;
/// Draw a frame around the control. /// Draw a frame around the control.
/// @param amt How much to offset the frame from the control's bounding rect. /// @param amt How much to offset the frame from the control's bounding rect.

View File

@@ -75,8 +75,10 @@ such, this attribute should be omitted for most dialogs.
to `true`, the XSL stylesheet will draw the bounding rects of LEDs and to `true`, the XSL stylesheet will draw the bounding rects of LEDs and
other debug information. other debug information.
* `fore` - The default text colour. Generally this shouldn't be needed. * `fore` - The default text colour. Generally this shouldn't be needed.
* `defbtn` - The ID (`name` attribute) of the default button. This is an * `defbtn` - The ID (`name` attribute) of button triggered by Enter/Return.
IDREF, so it must exist in the dialog. This is an IDREF, so it must exist in the dialog.
* `escbtn` - The ID (`name` attribute) of button triggered by Escape.
This is an IDREF, so it must exist in the dialog.
The `<text>` tag The `<text>` tag
---------------- ----------------