Skip to content

Commit 4efa817

Browse files
committed
Refactor dialog buttons to have equal width
1 parent e561435 commit 4efa817

12 files changed

Lines changed: 118 additions & 92 deletions

File tree

tabshell-core/src/main/java/com/techsenger/tabshell/core/dialog/AbstractDialogFxView.java

Lines changed: 82 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
package com.techsenger.tabshell.core.dialog;
1818

19+
import com.techsenger.annotations.Unmodifiable;
1920
import com.techsenger.tabshell.core.popup.AbstractPopupFxView;
2021
import com.techsenger.tabshell.material.button.ResultButton;
2122
import com.techsenger.tabshell.material.button.ResultButtonName;
@@ -27,13 +28,14 @@
2728
import com.techsenger.toolkit.fx.RegionResizer;
2829
import com.techsenger.toolkit.fx.Spacer;
2930
import com.techsenger.toolkit.fx.pulse.LayoutPhase;
30-
import com.techsenger.toolkit.fx.pulse.LayoutPulseListener;
3131
import com.techsenger.toolkit.fx.utils.ButtonUtils;
3232
import com.techsenger.toolkit.fx.value.ValueUtils;
33+
import java.util.Arrays;
3334
import java.util.HashMap;
3435
import java.util.List;
3536
import java.util.Map;
3637
import java.util.stream.Stream;
38+
import javafx.application.Platform;
3739
import javafx.beans.binding.Bindings;
3840
import javafx.beans.property.BooleanProperty;
3941
import javafx.beans.property.DoubleProperty;
@@ -44,7 +46,6 @@
4446
import javafx.geometry.Orientation;
4547
import javafx.geometry.Pos;
4648
import javafx.scene.Cursor;
47-
import javafx.scene.Node;
4849
import javafx.scene.control.Button;
4950
import javafx.scene.control.Label;
5051
import javafx.scene.input.MouseEvent;
@@ -116,8 +117,6 @@ public void remove() {
116117

117118
private final BooleanProperty resizable = new SimpleBooleanProperty();
118119

119-
private final BooleanProperty buttonWidthEqual = new SimpleBooleanProperty(false);
120-
121120
private final DoubleProperty minWidth = new SimpleDoubleProperty();
122121

123122
private final DoubleProperty minHeight = new SimpleDoubleProperty();
@@ -128,14 +127,6 @@ public void remove() {
128127

129128
private final Map<ResultButtonName, Button> buttonsByName = new HashMap<>();
130129

131-
private boolean buttonWidthListenerAdded = false;
132-
133-
private final LayoutPulseListener buttonWidthListener = () -> {
134-
makeResultButtonsEqual();
135-
buttonWidthListenerAdded = false;
136-
return false;
137-
};
138-
139130
/**
140131
* While dragging we need the difference. So, we keep in this variable previous value.
141132
*/
@@ -219,11 +210,6 @@ public void setIcon(Icon<?> icon) {
219210
}
220211
}
221212

222-
@Override
223-
public void setButtonWidthEqual(boolean value) {
224-
this.buttonWidthEqual.set(value);
225-
}
226-
227213
@Override
228214
public Composer getComposer() {
229215
return (Composer) super.getComposer();
@@ -274,10 +260,6 @@ public boolean isResizable() {
274260
return resizable.get();
275261
}
276262

277-
public boolean isButtonWidthEqual() {
278-
return buttonWidthEqual.get();
279-
}
280-
281263
protected BooleanProperty activeProperty() {
282264
return resizable;
283265
}
@@ -286,10 +268,6 @@ protected BooleanProperty resizableProperty() {
286268
return resizable;
287269
}
288270

289-
protected BooleanProperty buttonWidthEqualProperty() {
290-
return buttonWidthEqual;
291-
}
292-
293271
protected BooleanProperty outOfBoundsAllowedProperty() {
294272
return outOfBoundsAllowed;
295273
}
@@ -358,11 +336,6 @@ protected void bind() {
358336
@Override
359337
protected void addListeners() {
360338
super.addListeners();
361-
ValueUtils.callAndAddListener(this.buttonWidthEqual, (ov, oldV, newV) -> {
362-
if (newV) {
363-
updateButtonsEqual();
364-
}
365-
});
366339
ValueUtils.callAndAddListener(this.active, (ov, oldV, newV) -> {
367340
dialogBox.pseudoClassStateChanged(INACTIVE_PSEUDO_CLASS, !newV);
368341
});
@@ -382,27 +355,34 @@ protected void addHandlers() {
382355
}
383356

384357
/**
385-
* Makes all buttons in left and right boxes equal.
358+
* Makes the specified buttons equal in width.
386359
*/
387-
protected void makeResultButtonsEqual() {
388-
var buttons = getResultButtons();
389-
ButtonUtils.makeEqualWidthBySize(buttons, true);
360+
protected void makeEqualWidth(Button... buttons) {
361+
makeEqualWidth(Arrays.asList(buttons));
390362
}
391363

392364
/**
393-
* Returns added buttons from the left and right boxes.
394-
*
395-
* @return list of all added buttons or empty collection
365+
* Makes the specified buttons equal in width.
396366
*/
397-
protected List<ResultButton> getResultButtons() {
398-
Stream<Node> allChildren = Stream.concat(
399-
leftButtonBox.getChildren().stream(),
400-
rightButtonBox.getChildren().stream()
401-
);
402-
return allChildren
403-
.filter(ResultButton.class::isInstance)
404-
.map(ResultButton.class::cast)
405-
.toList();
367+
protected void makeEqualWidth(List<Button> buttons) {
368+
// To avoid flickering, both button boxes are hidden for one pulse:
369+
// [Before Pulse N] hide buttons, register POST-layout listener
370+
// [Pulse N | Layout] calculate sizes of invisible buttons
371+
// [Pulse N | POST] equalize button widths, schedule setVisible(true) via runLater
372+
// [Pulse N | Render] nothing visible, nothing rendered
373+
// [Between N and N+1] runLater executes → buttons marked visible + dirty
374+
// [Pulse N+1 | Layout] recalculate layout with correct minWidth
375+
// [Pulse N+1 | Render] buttons appear on screen with correct size
376+
rightButtonBox.setVisible(false);
377+
leftButtonBox.setVisible(false);
378+
getPulseListenerManager().addListener(LayoutPhase.POST, () -> {
379+
ButtonUtils.makeEqualWidthBySize(buttons, true);
380+
Platform.runLater(() -> {
381+
rightButtonBox.setVisible(true);
382+
leftButtonBox.setVisible(true);
383+
});
384+
return false;
385+
});
406386
}
407387

408388
/**
@@ -433,6 +413,43 @@ protected void unregisterButtons(ResultButton... buttons) {
433413
}
434414
}
435415

416+
/**
417+
* Returns an unmodifiable list of buttons located in the left button container.
418+
*
419+
* @param resultButtonsOnly if {@code true}, only buttons of type {@link ResultButton}
420+
* are returned; otherwise all {@link Button} instances are included
421+
* @return unmodifiable list of buttons from the left {@code HBox}
422+
*/
423+
protected @Unmodifiable List<Button> getLeftButtons(boolean resultButtonsOnly) {
424+
return getButtons(leftButtonBox, resultButtonsOnly);
425+
}
426+
427+
/**
428+
* Returns an unmodifiable list of buttons located in the right button container.
429+
*
430+
* @param resultButtonsOnly if {@code true}, only buttons of type {@link ResultButton}
431+
* are returned; otherwise all {@link Button} instances are included
432+
* @return unmodifiable list of buttons from the right {@code HBox}
433+
*/
434+
protected @Unmodifiable List<Button> getRightButtons(boolean resultButtonsOnly) {
435+
return getButtons(rightButtonBox, resultButtonsOnly);
436+
}
437+
438+
/**
439+
* Returns an unmodifiable combined list of buttons from both left and right button containers.
440+
*
441+
* <p>If {@code resultButtonsOnly} is {@code true}, only {@link ResultButton} instances are included.</p>
442+
*
443+
* @param resultButtonsOnly filter flag for selecting only result buttons
444+
* @return unmodifiable list of all matching buttons from both sides
445+
*/
446+
protected @Unmodifiable List<Button> getButtons(boolean resultButtonsOnly) {
447+
return Stream.concat(
448+
getLeftButtons(resultButtonsOnly).stream(),
449+
getRightButtons(resultButtonsOnly).stream())
450+
.toList();
451+
}
452+
436453
/**
437454
* Returns a button box that can contain both result and additional buttons. In other words, it is safe to
438455
* add custom buttons to this box.
@@ -510,7 +527,6 @@ private void addButtons(HBox box, ResultButtonName... names) {
510527
box.getChildren().add(button);
511528
}
512529
}
513-
updateButtonsEqual();
514530
updateTrap();
515531
}
516532

@@ -520,11 +536,25 @@ private void removeButtons(HBox box) {
520536
);
521537
}
522538

523-
private List<ResultButtonName> getButtons(HBox box) {
524-
return box.getChildren().stream()
525-
.filter(ResultButton.class::isInstance)
526-
.map(ResultButton.class::cast)
527-
.map(b -> b.getName())
539+
/**
540+
* Extracts buttons from the given {@link HBox} container with optional filtering by type.
541+
*
542+
* <p>If {@code resultButtonsOnly} is {@code true}, only instances of {@link ResultButton}
543+
* are included; otherwise all {@link Button} instances are returned.</p>
544+
*
545+
* @param buttonBox the container holding button nodes
546+
* @param resultButtonsOnly whether to include only {@link ResultButton} instances
547+
* @return unmodifiable list of buttons contained in the specified box
548+
*/
549+
private @Unmodifiable List<Button> getButtons(HBox buttonBox, boolean resultButtonsOnly) {
550+
Class<? extends Button> filterClass = Button.class;
551+
if (resultButtonsOnly) {
552+
filterClass = ResultButton.class;
553+
}
554+
555+
return buttonBox.getChildren().stream()
556+
.filter(filterClass::isInstance)
557+
.map(Button.class::cast)
528558
.toList();
529559
}
530560

@@ -533,11 +563,4 @@ private void updateTrap() {
533563
this.focusTrap.update();
534564
}
535565
}
536-
537-
private void updateButtonsEqual() {
538-
if (!this.buttonWidthListenerAdded) {
539-
getPulseListenerManager().addListener(LayoutPhase.POST, buttonWidthListener);
540-
buttonWidthListenerAdded = true;
541-
}
542-
}
543566
}

tabshell-core/src/main/java/com/techsenger/tabshell/core/dialog/AbstractDialogPresenter.java

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -76,8 +76,6 @@ public void setDisabled(boolean disabled) {
7676

7777
private boolean resizable;
7878

79-
private boolean buttonWidthEqual;
80-
8179
private boolean closeDisabled;
8280

8381
private String title;
@@ -178,16 +176,6 @@ public void setResizable(boolean resizable) {
178176
getView().setResizable(resizable);
179177
}
180178

181-
@Override
182-
public boolean isButtonWidthEqual() {
183-
return buttonWidthEqual;
184-
}
185-
186-
public void setButtonWidthEqual(boolean buttonWidthEqual) {
187-
this.buttonWidthEqual = buttonWidthEqual;
188-
getView().setButtonWidthEqual(buttonWidthEqual);
189-
}
190-
191179
@Override
192180
public boolean isCloseDisabled() {
193181
return closeDisabled;

tabshell-core/src/main/java/com/techsenger/tabshell/core/dialog/ReadOnlyDialog.java

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -78,13 +78,6 @@ public interface ReadOnlyDialog {
7878
*/
7979
boolean isResizable();
8080

81-
/**
82-
* Returns whether equal button width rendering is enabled.
83-
*
84-
* @return {@code true} if buttons should have equal width, {@code false} otherwise
85-
*/
86-
boolean isButtonWidthEqual();
87-
8881
/**
8982
* Returns whether the close button in the top right corner of the dialog is disabled.
9083
*

tabshell-core/src/main/java/com/techsenger/tabshell/core/dialog/WriteOnlyDialog.java

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -81,16 +81,6 @@ public interface WriteOnlyDialog {
8181
*/
8282
void setResizable(boolean value);
8383

84-
/**
85-
* Enables or disables equal button width rendering.
86-
* <p>
87-
* When set to {@code true}, the view may re-evaluate button sizes and adjust their widths according to the
88-
* current layout.
89-
*
90-
* @param value {@code true} to enable equal button widths, {@code false} to disable
91-
*/
92-
void setButtonWidthEqual(boolean value);
93-
9484
/**
9585
* Sets the disabled state for the close button in the top right corner of the dialog.
9686
*

tabshell-demo/src/main/java/com/techsenger/tabshell/demo/dialogs/DemoDialogFxView.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
import com.techsenger.tabshell.core.dialog.AbstractDialogFxView;
2020
import com.techsenger.tabshell.material.button.ResultButton;
21+
import com.techsenger.tabshell.material.button.ResultButtonName;
2122
import com.techsenger.tabshell.material.style.Spacing;
2223
import javafx.scene.control.Label;
2324
import javafx.scene.control.TextField;
@@ -55,6 +56,12 @@ public void requestFocus() {
5556
fooTextField.deselect();
5657
}
5758

59+
@Override
60+
public void setRightButtons(ResultButtonName... names) {
61+
super.setRightButtons(names);
62+
makeEqualWidth(getRightButtons(true));
63+
}
64+
5865
@Override
5966
protected void build() {
6067
super.build();

tabshell-demo/src/main/java/com/techsenger/tabshell/demo/dialogs/DemoDialogPresenter.java

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,6 @@ protected void postInitialize() {
5555
setResizable(true);
5656
setPrefWidth(500);
5757
setTitle("Demo Dialog");
58-
setButtonWidthEqual(true); //cancel button width will be equal to ok button width
5958
setRightButtons(DemoResultButtons.CANCEL, DemoResultButtons.OK);
6059
}
6160
}

tabshell-demo/src/main/java/com/techsenger/tabshell/demo/dialogs/DialogsDialogFxView.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
import com.techsenger.tabshell.dialogs.namevalue.NameValueDialogPort;
3737
import com.techsenger.tabshell.dialogs.namevalue.NameValueDialogPresenter;
3838
import com.techsenger.tabshell.material.button.ResultButton;
39+
import com.techsenger.tabshell.material.button.ResultButtonName;
3940
import java.util.List;
4041
import javafx.collections.FXCollections;
4142
import javafx.scene.control.ListCell;
@@ -124,6 +125,12 @@ public Composer getComposer() {
124125
return (Composer) super.getComposer();
125126
}
126127

128+
@Override
129+
public void setRightButtons(ResultButtonName... names) {
130+
super.setRightButtons(names);
131+
makeEqualWidth(getRightButtons(true));
132+
}
133+
127134
@Override
128135
protected Composer createComposer() {
129136
return new DialogsDialogFxView.Composer();

tabshell-demo/src/main/java/com/techsenger/tabshell/demo/dialogs/DialogsDialogPresenter.java

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,6 @@ protected void postInitialize() {
9797
setPrefWidth(600);
9898
setPrefHeight(300);
9999
setTitle("Dialogs");
100-
setButtonWidthEqual(true);
101100
view.setDialogTypes(Arrays.asList(DialogType.values()));
102101
setResultAction((result) -> {
103102
requestClose();

tabshell-dialogs/src/main/java/com/techsenger/tabshell/dialogs/alert/AlertDialogFxView.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import static com.techsenger.tabshell.dialogs.alert.AlertDialogType.ERROR;
2222
import com.techsenger.tabshell.dialogs.utils.FxViewUtils;
2323
import com.techsenger.tabshell.material.button.ResultButton;
24+
import com.techsenger.tabshell.material.button.ResultButtonName;
2425
import com.techsenger.tabshell.material.icon.Icon;
2526
import com.techsenger.tabshell.material.icon.IconViewBox;
2627
import com.techsenger.toolkit.fx.utils.NodeUtils;
@@ -67,6 +68,16 @@ public void requestFocus() {
6768
NodeUtils.requestFocus(getNode());
6869
}
6970

71+
@Override
72+
public void setRightButtons(ResultButtonName... names) {
73+
super.setRightButtons(names);
74+
makeButtonsEqualWidth();
75+
}
76+
77+
protected void makeButtonsEqualWidth() {
78+
makeEqualWidth(getRightButtons(true));
79+
}
80+
7081
@Override
7182
protected void build() {
7283
super.build();

tabshell-dialogs/src/main/java/com/techsenger/tabshell/dialogs/alert/AlertDialogPresenter.java

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,5 @@ protected void postInitialize() {
118118
setResultAction((result) -> requestClose());
119119
setPrefWidth(600);
120120
setMessage(message);
121-
setButtonWidthEqual(true);
122121
}
123122
}

0 commit comments

Comments
 (0)