Lay the join row out from its right edge
The box asked for a fixed width and the button took what was left, which holds only while the window is wider than that number. Under it the entry took the row and the button wrapped to a line of its own — Join under the box rather than beside it. Measuring the window instead moved the wrap rather than removing it: the width is polled a few times a second, so a drag wrapped for a tick and then resized. So the row is laid out from the right: `:align :end` with the button first, placed against that edge, and the box after it asking for whatever remains where it stands. The box is the part that gives, at every width, in the frame the drag happens. The search row goes the same way, its clear button leading for the same reason. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
4a372ae parent: cb7f9f1 modified
src/frq/app.jolt +20 -25 | @@ -162,19 +162,6 @@ | ||
| 162 | 162 | |
| 163 | 163 | (def ^:private sidebar-width 320) |
| 164 | 164 | |
| 165 | -(defn- control-entry-width | |
| 166 | - "How wide the join and search boxes ask to be. | |
| 167 | - | |
| 168 | - A fixed 380 is right on a window that holds the list alone, and too wide for | |
| 169 | - the list as a split view's left column: the row it sits on is only | |
| 170 | - `sidebar-width` across, and an entry that asks for more than remains pushes | |
| 171 | - the button beside it onto a row of its own — Join under the box rather than | |
| 172 | - next to it. Beside the entry go the button and the gap, and around the row | |
| 173 | - the card's and the page's padding, so the column's share is what is left of | |
| 174 | - the sidebar after those." | |
| 175 | - [] | |
| 176 | - (if (s/wide?) (- sidebar-width 130) 380)) | |
| 177 | - | |
| 178 | 165 | (defn- below-list [] |
| 179 | 166 | ;; What the strip under the list needs, counted: the gap after the list, the |
| 180 | 167 | ;; separator, the gap under it, the tab bar's row of buttons and the margin |
| @@ -207,27 +194,35 @@ | ||
| 207 | 194 | ;; channel" header was doing. |
| 208 | 195 | [:card {} |
| 209 | 196 | [:vbox {:spacing 8} |
| 210 | - [:hbox {:spacing 8} | |
| 197 | + ;; Button first and `:align :end`, so the row is laid out from its | |
| 198 | + ;; right edge: the button is placed against that edge and the box takes | |
| 199 | + ;; what is left. An entry asks for whatever remains where it stands, so | |
| 200 | + ;; a box placed first takes the row and pushes the button onto a line of | |
| 201 | + ;; its own — Join under the box rather than beside it — and a box given | |
| 202 | + ;; a fixed width only holds that off until the window is narrower than | |
| 203 | + ;; the number. Laid out from the right the box is the part that gives, | |
| 204 | + ;; at every width, in the frame the drag happens rather than the one | |
| 205 | + ;; after the window is next measured. | |
| 206 | + [:hbox {:spacing 8 :align :end} | |
| 207 | + [:button {:label (if (str/starts-with? @s/join-input "@") "Message" "Join") | |
| 208 | + :kind :primary | |
| 209 | + :on-click #(do (s/join! @s/join-input) (reset! s/join-input ""))}] | |
| 211 | 210 | ;; Both kinds of conversation through one box: `#room` joins a |
| 212 | 211 | ;; channel, `@nick` opens a message to a person. |
| 213 | 212 | [:entry {:text @s/join-input |
| 214 | - :width-request (control-entry-width) | |
| 215 | 213 | :placeholder "#channel or @nick" |
| 216 | 214 | :on-change #(reset! s/join-input %) |
| 217 | - :on-activate #(do (s/join! @s/join-input) (reset! s/join-input ""))}] | |
| 218 | - [:button {:label (if (str/starts-with? @s/join-input "@") "Message" "Join") | |
| 219 | - :kind :primary | |
| 220 | - :on-click #(do (s/join! @s/join-input) (reset! s/join-input ""))}]] | |
| 215 | + :on-activate #(do (s/join! @s/join-input) (reset! s/join-input ""))}]] | |
| 221 | 216 | ;; The clear button only shows while there is something to clear: an |
| 222 | 217 | ;; empty box has nothing to undo, and a dead button beside it reads as |
| 223 | - ;; one that stopped working. | |
| 224 | - [:hbox {:spacing 8} | |
| 218 | + ;; one that stopped working. It leads the row for the same reason the | |
| 219 | + ;; Join button does, and the box gives up the width it takes. | |
| 220 | + [:hbox {:spacing 8 :align :end} | |
| 221 | + (when (seq @s/search) | |
| 222 | + [:button {:label "✕" :on-click #(reset! s/search "")}]) | |
| 225 | 223 | [:entry {:text @s/search |
| 226 | - :width-request (control-entry-width) | |
| 227 | 224 | :placeholder "Search channels" |
| 228 | - :on-change #(reset! s/search %)}] | |
| 229 | - (when (seq @s/search) | |
| 230 | - [:button {:label "✕" :on-click #(reset! s/search "")}])]]]] | |
| 225 | + :on-change #(reset! s/search %)}]]]]] | |
| 231 | 226 | ;; `:fill-height` so the scroll inside is handed the rest of the window |
| 232 | 227 | ;; rather than the one card it starts out holding, and `:reserve` to keep |
| 233 | 228 | ;; the tabs' strip out of what it may take. |
| @@ -162,19 +162,6 @@ | |||
| 162 | 162 | ||
| 163 | (def ^:private sidebar-width 320) | 163 | (def ^:private sidebar-width 320) |
| 164 | 164 | ||
| 165 | -(defn- control-entry-width | ||
| 166 | - "How wide the join and search boxes ask to be. | ||
| 167 | - | ||
| 168 | - A fixed 380 is right on a window that holds the list alone, and too wide for | ||
| 169 | - the list as a split view's left column: the row it sits on is only | ||
| 170 | - `sidebar-width` across, and an entry that asks for more than remains pushes | ||
| 171 | - the button beside it onto a row of its own — Join under the box rather than | ||
| 172 | - next to it. Beside the entry go the button and the gap, and around the row | ||
| 173 | - the card's and the page's padding, so the column's share is what is left of | ||
| 174 | - the sidebar after those." | ||
| 175 | - [] | ||
| 176 | - (if (s/wide?) (- sidebar-width 130) 380)) | ||
| 177 | - | ||
| 178 | (defn- below-list [] | 165 | (defn- below-list [] |
| 179 | ;; What the strip under the list needs, counted: the gap after the list, the | 166 | ;; What the strip under the list needs, counted: the gap after the list, the |
| 180 | ;; separator, the gap under it, the tab bar's row of buttons and the margin | 167 | ;; separator, the gap under it, the tab bar's row of buttons and the margin |
| @@ -207,27 +194,35 @@ | |||
| 207 | ;; channel" header was doing. | 194 | ;; channel" header was doing. |
| 208 | [:card {} | 195 | [:card {} |
| 209 | [:vbox {:spacing 8} | 196 | [:vbox {:spacing 8} |
| 210 | - [:hbox {:spacing 8} | 197 | + ;; Button first and `:align :end`, so the row is laid out from its |
| 198 | + ;; right edge: the button is placed against that edge and the box takes | ||
| 199 | + ;; what is left. An entry asks for whatever remains where it stands, so | ||
| 200 | + ;; a box placed first takes the row and pushes the button onto a line of | ||
| 201 | + ;; its own — Join under the box rather than beside it — and a box given | ||
| 202 | + ;; a fixed width only holds that off until the window is narrower than | ||
| 203 | + ;; the number. Laid out from the right the box is the part that gives, | ||
| 204 | + ;; at every width, in the frame the drag happens rather than the one | ||
| 205 | + ;; after the window is next measured. | ||
| 206 | + [:hbox {:spacing 8 :align :end} | ||
| 207 | + [:button {:label (if (str/starts-with? @s/join-input "@") "Message" "Join") | ||
| 208 | + :kind :primary | ||
| 209 | + :on-click #(do (s/join! @s/join-input) (reset! s/join-input ""))}] | ||
| 211 | ;; Both kinds of conversation through one box: `#room` joins a | 210 | ;; Both kinds of conversation through one box: `#room` joins a |
| 212 | ;; channel, `@nick` opens a message to a person. | 211 | ;; channel, `@nick` opens a message to a person. |
| 213 | [:entry {:text @s/join-input | 212 | [:entry {:text @s/join-input |
| 214 | - :width-request (control-entry-width) | ||
| 215 | :placeholder "#channel or @nick" | 213 | :placeholder "#channel or @nick" |
| 216 | :on-change #(reset! s/join-input %) | 214 | :on-change #(reset! s/join-input %) |
| 217 | - :on-activate #(do (s/join! @s/join-input) (reset! s/join-input ""))}] | 215 | + :on-activate #(do (s/join! @s/join-input) (reset! s/join-input ""))}]] |
| 218 | - [:button {:label (if (str/starts-with? @s/join-input "@") "Message" "Join") | ||
| 219 | - :kind :primary | ||
| 220 | - :on-click #(do (s/join! @s/join-input) (reset! s/join-input ""))}]] | ||
| 221 | ;; The clear button only shows while there is something to clear: an | 216 | ;; The clear button only shows while there is something to clear: an |
| 222 | ;; empty box has nothing to undo, and a dead button beside it reads as | 217 | ;; empty box has nothing to undo, and a dead button beside it reads as |
| 223 | - ;; one that stopped working. | 218 | + ;; one that stopped working. It leads the row for the same reason the |
| 224 | - [:hbox {:spacing 8} | 219 | + ;; Join button does, and the box gives up the width it takes. |
| 220 | + [:hbox {:spacing 8 :align :end} | ||
| 221 | + (when (seq @s/search) | ||
| 222 | + [:button {:label "✕" :on-click #(reset! s/search "")}]) | ||
| 225 | [:entry {:text @s/search | 223 | [:entry {:text @s/search |
| 226 | - :width-request (control-entry-width) | ||
| 227 | :placeholder "Search channels" | 224 | :placeholder "Search channels" |
| 228 | - :on-change #(reset! s/search %)}] | 225 | + :on-change #(reset! s/search %)}]]]]] |
| 229 | - (when (seq @s/search) | ||
| 230 | - [:button {:label "✕" :on-click #(reset! s/search "")}])]]]] | ||
| 231 | ;; `:fill-height` so the scroll inside is handed the rest of the window | 226 | ;; `:fill-height` so the scroll inside is handed the rest of the window |
| 232 | ;; rather than the one card it starts out holding, and `:reserve` to keep | 227 | ;; rather than the one card it starts out holding, and `:reserve` to keep |
| 233 | ;; the tabs' strip out of what it may take. | 228 | ;; the tabs' strip out of what it may take. |