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 8242c1e4bd
commit 8af5eb6c38
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-stylesheet href="dialog.xsl" type="text/xsl"?>
<dialog defbtn='okay'>
<dialog defbtn='okay' escbtn='okay'>
<pict type='dlog' num='16' top='8' left='8'/>
<text top='8' left='50' width='400' height='16'>DEBUG MODE HELP</text>
<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="fore" default="black"/>
<xs:attribute name="defbtn" type="xs:token"/>
<xs:attribute name="escbtn" type="xs:token"/>
</xs:complexType>
<xs:unique name="FieldTabOrder">
<xs:selector xpath="*" />
@@ -411,6 +412,10 @@
<xs:selector xpath="."/>
<xs:field xpath="@defbtn"/>
</xs:keyref>
<xs:keyref name="escapeButton" refer="uniqueID">
<xs:selector xpath="."/>
<xs:field xpath="@escbtn"/>
</xs:keyref>
<xs:keyref name="sliderLink" refer="uniqueID">
<xs:selector xpath="slider"/>
<xs:field xpath="@link"/>

View File

@@ -158,7 +158,7 @@ void cDialog::loadFromFile(const DialogDefn& file){
Iterator<Attribute> attr;
Iterator<Element> node;
string type, name, val, defbtn;
string type, name, val, defbtn, escbtn;
xml.FirstChildElement()->GetValue(&type);
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);
}
defTextClr = clr;
} else if(name == "defbtn") {
}else if(name == "defbtn"){
defbtn = val;
}else if(name == "escbtn"){
escbtn = val;
}else if(name != "debug")
throw xBadAttr(type,name,attr->Row(),attr->Column(),fname);
}
@@ -337,6 +339,8 @@ void cDialog::loadFromFile(const DialogDefn& file){
// Set the default button.
setDefaultButton(defbtn);
// Set the escape button.
setEscapeButton(escbtn);
// Sort by 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){
ctrlIter iter = controls.begin();
bool enterKeyHit = keyHit.spec && keyHit.k == key_enter;
bool escapeKeyHit = keyHit.spec && keyHit.k == key_esc;
while(iter != controls.end()){
cControl* ctrl = iter->second;
if(ctrl->isVisible()){
if(ctrl->isClickable() &&
(ctrl->getAttachedKey() == keyHit || (ctrl->isDefault() && enterKeyHit))){
(ctrl->getAttachedKey() == keyHit || (ctrl->isDefault() && enterKeyHit) || (ctrl->isEscape() && escapeKeyHit))){
ctrl->handleKeyTriggered(*this);
return;
@@ -940,11 +945,6 @@ void cDialog::process_keystroke(cKey keyHit){
}
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){
@@ -1157,21 +1157,38 @@ bool cDialog::hasControl(std::string id) const {
return false;
}
void cDialog::setDefaultButton(std::string defbtn) {
if(!defbtn.empty() && !hasControl(defbtn)){
void cDialog::setSpecialButton(std::string& name_ref, std::string name, bool escape) {
if(!name.empty() && !hasControl(name)){
// this is likely because the dialogxml is malformed. maybe the linter already checks this,
// 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()){
getControl(defaultButton).setDefault(false);
if(!name_ref.empty()){
if(escape){
getControl(name_ref).setEscape(false);
}else{
getControl(name_ref).setDefault(false);
}
name_ref = "";
}
if(!defbtn.empty()){
defaultButton = defbtn;
getControl(defaultButton).setDefault(true);
if(!name.empty()){
name_ref = name;
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$";
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 setDoAnimations(bool value) { doAnimations = value; }
void setDefaultButton(std::string defbtn);
void setEscapeButton(std::string escbtn);
private:
void setSpecialButton(std::string& name_ref, std::string name, bool escape);
void draw();
void handle_events();
void handle_one_event(const sf::Event&, cFramerateLimiter& fps_limiter);
@@ -277,6 +279,7 @@ private:
boost::any result;
std::string fname;
std::string defaultButton;
std::string escapeButton;
sf::Clock animTimer, paintTimer;
friend class cControl;
friend class cContainer;

View File

@@ -172,6 +172,8 @@ public:
std::string getAttachedKeyDescription() const;
inline void setDefault(bool value) { isDefaultControl = value; }
inline bool isDefault() { return isDefaultControl; }
inline void setEscape(bool value) { isEscapeControl = value; }
inline bool isEscape() { return isEscapeControl; }
/// Attach an event handler to this control.
/// @tparam t The type of event to attach.
/// @param handler The event handler function or functor. Its signature depends on the event type.
@@ -477,6 +479,8 @@ protected:
cKey key;
/// Whether the control is the default control of its dialog.
bool isDefaultControl = false;
/// Whether the control is the cancel control of its dialog.
bool isEscapeControl = false;
/// Draw a frame around the control.
/// @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
other debug information.
* `fore` - The default text colour. Generally this shouldn't be needed.
* `defbtn` - The ID (`name` attribute) of the default button. This is an
IDREF, so it must exist in the dialog.
* `defbtn` - The ID (`name` attribute) of button triggered by Enter/Return.
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
----------------