nandi/frqpublic Fork 0
8f05e74
Commits
Clone
git clone https://git.rickub.com/nandi/frq.git
git clone ssh://git@rickub.com/nandi/frq.git

Host key fingerprint (ed25519): SHA256:iycHnxEyq0Q7uyVpB7JlznP0G7JrTPXLYRcAU5CSLhc — verify it before your first connect.

Open a profile by pressing a face, and nothing else

Resting a pointer on a face raised the card too, with a 400ms grace period
for crossing from the face to it and the card reporting its own pointer back
so that leaving the face was not the end of it. That bought a second way in
at a price: the card arrived unasked for on every face crossed on the way to
the scrollbar, and the dialog had to be non-modal whenever a pointer was
what held it open — a modal one makes the window underneath it deaf, so the
face would never hear the pointer leave and could not close what it opened.

One gesture now, on both desktop backends and the phone alike. The dialog is
unconditionally modal, which is what it always wanted to be, and reads
`viewing` alone; `dismiss!` collapses into `close!`, since there is only one
thing left to clear.

The grace period was `frq.io/after!`'s only caller, so the seam entry and
both host implementations go with it. `frq.profile.pointer` is renamed to
`frq.profile.host`: what is left in it is this backend's fetch and its
avatar, and no pointer at all. The reaction-hover card is untouched — it is
still the pointer's, and still non-modal for the same reason this one no
longer has to be.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
nandi committed 2026-09-12T10:16:15-07:00 Browse files
8f05e74 parent: 54b668a
modified common/frq/actions.cljc +0 -6
@@ -90,7 +90,6 @@
9090
9191 (defn desktop? [] (boolean (call :desktop? [])))
9292 (defn quit! [] (call :quit! []))
93-(defn hovering [] (call :hovering []))
9493 (defn viewing [] (call :viewing []))
9594 (defn accept-policy! [& args] (call :accept-policy! args))
9695 (defn close-picker! [& args] (call :close-picker! args))
@@ -135,8 +134,6 @@
135134
136135 ;; ---------------------------------------------------------------- profile
137136
138-(defn profile-hover! [& args] (call :profile-hover! args))
139-(defn profile-unhover! [& args] (call :profile-unhover! args))
140137 (defn profile-open! [& args] (call :profile-open! args))
141138 (defn toggle-hide-join-part! [] (call :toggle-hide-join-part! []))
142139
@@ -156,10 +153,7 @@
156153 (defn avatar-ready [& args] (call :avatar-ready args))
157154
158155 (defn profile-close! [& args] (call :profile-close! args))
159-(defn profile-dismiss! [& args] (call :profile-dismiss! args))
160-(defn profile-enter-dialog! [& args] (call :profile-enter-dialog! args))
161156 (defn profile-entry [& args] (call :profile-entry args))
162-(defn profile-leave-dialog! [& args] (call :profile-leave-dialog! args))
163157 (defn profile-stats-line [& args] (call :profile-stats-line args))
164158 (defn profile-tick [] (call :profile-tick []))
165159 (defn profile-truncate [& args] (call :profile-truncate args))
@@ -90,7 +90,6 @@
90 90
91 (defn desktop? [] (boolean (call :desktop? [])))91 (defn desktop? [] (boolean (call :desktop? [])))
92 (defn quit! [] (call :quit! []))92 (defn quit! [] (call :quit! []))
93-(defn hovering [] (call :hovering []))
94 (defn viewing [] (call :viewing []))93 (defn viewing [] (call :viewing []))
95 (defn accept-policy! [& args] (call :accept-policy! args))94 (defn accept-policy! [& args] (call :accept-policy! args))
96 (defn close-picker! [& args] (call :close-picker! args))95 (defn close-picker! [& args] (call :close-picker! args))
@@ -135,8 +134,6 @@
135 134
136 ;; ---------------------------------------------------------------- profile135 ;; ---------------------------------------------------------------- profile
137 136
138-(defn profile-hover! [& args] (call :profile-hover! args))
139-(defn profile-unhover! [& args] (call :profile-unhover! args))
140 (defn profile-open! [& args] (call :profile-open! args))137 (defn profile-open! [& args] (call :profile-open! args))
141 (defn toggle-hide-join-part! [] (call :toggle-hide-join-part! []))138 (defn toggle-hide-join-part! [] (call :toggle-hide-join-part! []))
142 139
@@ -156,10 +153,7 @@
156 (defn avatar-ready [& args] (call :avatar-ready args))153 (defn avatar-ready [& args] (call :avatar-ready args))
157 154
158 (defn profile-close! [& args] (call :profile-close! args))155 (defn profile-close! [& args] (call :profile-close! args))
159-(defn profile-dismiss! [& args] (call :profile-dismiss! args))
160-(defn profile-enter-dialog! [& args] (call :profile-enter-dialog! args))
161 (defn profile-entry [& args] (call :profile-entry args))156 (defn profile-entry [& args] (call :profile-entry args))
162-(defn profile-leave-dialog! [& args] (call :profile-leave-dialog! args))
163 (defn profile-stats-line [& args] (call :profile-stats-line args))157 (defn profile-stats-line [& args] (call :profile-stats-line args))
164 (defn profile-tick [] (call :profile-tick []))158 (defn profile-tick [] (call :profile-tick []))
165 (defn profile-truncate [& args] (call :profile-truncate args))159 (defn profile-truncate [& args] (call :profile-truncate args))
modified common/frq/cells.cljc +1 -5
@@ -238,10 +238,6 @@
238238 ;; or handle, and nil for a guest.
239239 (defonce profile-viewing (atom nil))
240240
241-;; The face the pointer is resting on, the same shape. A card is painted for
242-;; this one alone rather than hung under every avatar in the column.
243-(defonce profile-hovering (atom nil))
244-
245241 ;; Bumped when a profile fetch lands, so the screen re-renders without
246242 ;; watching the cache map itself.
247243 (defonce profile-tick (atom 0))
@@ -273,4 +269,4 @@
273269 attachment jump-tick show-users? hide-chat-list? overview? at-present?
274270 emoji-group emoji-search highlight jump-to lightbox overview-return
275271 saved-to reacting window-height window-width reaction-hover hide-join-part?
276- image-picker profile-viewing profile-hovering profile-tick])
272+ image-picker profile-viewing profile-tick])
@@ -238,10 +238,6 @@
238 ;; or handle, and nil for a guest.238 ;; or handle, and nil for a guest.
239 (defonce profile-viewing (atom nil))239 (defonce profile-viewing (atom nil))
240 240
241-;; The face the pointer is resting on, the same shape. A card is painted for
242-;; this one alone rather than hung under every avatar in the column.
243-(defonce profile-hovering (atom nil))
244-
245 ;; Bumped when a profile fetch lands, so the screen re-renders without241 ;; Bumped when a profile fetch lands, so the screen re-renders without
246 ;; watching the cache map itself.242 ;; watching the cache map itself.
247 (defonce profile-tick (atom 0))243 (defonce profile-tick (atom 0))
@@ -273,4 +269,4 @@
273 attachment jump-tick show-users? hide-chat-list? overview? at-present?269 attachment jump-tick show-users? hide-chat-list? overview? at-present?
274 emoji-group emoji-search highlight jump-to lightbox overview-return270 emoji-group emoji-search highlight jump-to lightbox overview-return
275 saved-to reacting window-height window-width reaction-hover hide-join-part?271 saved-to reacting window-height window-width reaction-hover hide-join-part?
276- image-picker profile-viewing profile-hovering profile-tick])272+ image-picker profile-viewing profile-tick])
modified common/frq/io.cljc +0 -12
@@ -142,15 +142,3 @@
142142 backlog read in November carries messages from August."
143143 [epoch-secs]
144144 (call :local-offset-seconds [epoch-secs]))
145-
146-(defn after!
147- "Run `f` in about `ms` milliseconds, wherever this host runs UI work.
148-
149- Named for when rather than for how, like the rest of this seam. The window
150- lends the toolkit's own timer — a callback off the UI thread repaints from
151- the wrong one — and the phone has an event loop already and needs no
152- lending. What hangs on it is the grace period for crossing from a face to
153- the card it raised: see `frq.profile/unhover!`."
154- [ms f]
155- (call :after! [ms f])
156- nil)
@@ -142,15 +142,3 @@
142 backlog read in November carries messages from August."142 backlog read in November carries messages from August."
143 [epoch-secs]143 [epoch-secs]
144 (call :local-offset-seconds [epoch-secs]))144 (call :local-offset-seconds [epoch-secs]))
145-
146-(defn after!
147- "Run `f` in about `ms` milliseconds, wherever this host runs UI work.
148-
149- Named for when rather than for how, like the rest of this seam. The window
150- lends the toolkit's own timer — a callback off the UI thread repaints from
151- the wrong one — and the phone has an event loop already and needs no
152- lending. What hangs on it is the grace period for crossing from a face to
153- the card it raised: see `frq.profile/unhover!`."
154- [ms f]
155- (call :after! [ms f])
156- nil)
modified common/frq/profile.cljc +13 -90
@@ -11,22 +11,22 @@
1111 them the screen is the nick and a line saying so, which is the honest answer
1212 rather than a spinner that never lands.
1313
14- What a pointer does about one is here too, at the foot of this file: hovering
15- a face, the grace period for crossing from the face to the card, and the card
16- reporting its own pointer. That used to be jolt's alone, on the reading that
17- a pointer meant libcosmic — but `just flutter-desktop` is a window with a
18- mouse in it as much as `just cosmic run` is, and the machine is a few atoms
19- and a timer with nothing host-shaped in it. The timer is the one thing that was,
20- and `frq.io/after!` is where that went. Whether there is a pointer at all is
21- still the host's answer: `actions/desktop?`.
14+ One gesture opens it, on every backend: a press. A pointer resting on a face
15+ used to open the card too, with a grace period for crossing from the face to
16+ it and the card reporting its own pointer back, so that leaving the face was
17+ not the end of it. That is gone. It made the card something that could arrive
18+ and leave without being asked for crossing a column of faces on the way to
19+ the scrollbar flickered one open per row and it bought a second way in at
20+ the price of a dialog that had to be non-modal to be able to close itself.
21+ Pressing a face is the whole of it now, and what it opens stays until it is
22+ closed.
2223
2324 The fetch is a seam, because it is the one part that differs a future and a
2425 blocking request on one side, an awaited one on the other. Nothing is fetched
2526 until a backend installs one, and `entry` simply answers nil."
2627 (:require [clojure.string :as str]
2728 [frq.atproto.core :as atproto]
28- [frq.cells :as cells]
29- [frq.io :as io]))
29+ [frq.cells :as cells]))
3030
3131 (def directory-host atproto/directory-host)
3232
@@ -37,7 +37,6 @@
3737 (defonce ^:private cache (atom {}))
3838
3939 (def viewing cells/profile-viewing)
40-(def hovering cells/profile-hovering)
4140 (def tick cells/profile-tick)
4241
4342 ;; A handle is a domain: labels joined by dots, ending in something alphabetic.
@@ -153,86 +152,10 @@
153152 (reset! viewing {:nick nick :actor actor})
154153 (fetch! actor))
155154
156-(defn close! [] (reset! viewing nil))
157-
158-;; ------------------------------------------------------- what a pointer does
159-
160-;; Whether the pointer is on the dialog the hover put up.
161-;;
162-;; This is what makes a hovered profile something you can move into and read
163-;; rather than something you can only glance at: the dialog reports its own
164-;; pointer, so leaving the face is not the end of the hover if the pointer
165-;; turned up here instead.
166-(defonce ^:private over-dialog? (atom false))
167-
168-(defn dismiss!
169- "Put the profile away, however it was opened.
170-
171- The dialog is shown for `viewing` or for `hovering`, so a Close that cleared
172- only the first left one the pointer had opened on screen with its own button
173- doing nothing to it."
174- []
175- (reset! viewing nil)
176- (reset! hovering nil)
177- ;; And the pointer's claim on it. Close takes the dialog out from under the
178- ;; pointer, so there is no leaving edge coming to say so — left set, it
179- ;; would hold the next hover open for good.
180- (reset! over-dialog? false))
181-
182-;; How long the pointer may be on neither the face nor the dialog before the
183-;; dialog goes.
184-;;
185-;; There is a gap between the two — the dialog is centred and the face is
186-;; wherever the message is — and a hover that ended the instant the pointer
187-;; left the face would close it halfway across every time. Long enough to
188-;; cross, short enough that a pointer moving somewhere else entirely does not
189-;; drag it along.
190-(def ^:private grace-ms 400)
191-
192-(defn- release!
193- "Let `nick`'s hover go, unless something has taken it up again.
194-
195- Three things can have happened in the grace period: the pointer arrived on
196- the dialog, it went back to the face, or it landed on someone else's. In all
197- three there is a hover to keep, and it is not this one's to end — which is
198- what the nick guard says."
199- [nick]
200- (when-not @over-dialog?
201- (swap! hovering #(when-not (= nick (:nick %)) %))))
202-
203-(defn hover!
204- "The pointer has come to rest on someone's face. Starts the fetch opening
205- them would, so the card has something on it by the time it is read.
206-
207- Their picture is not asked for here. It is the one part of this that needs
208- the host the desktop downloads a file and the phone hands the CDN URL
209- straight to the widget so each backend asks for it at the seam, beside the
210- call to this."
211- [nick actor]
212- (reset! hovering {:nick nick :actor actor})
213- (fetch! actor))
214-
215-(defn unhover!
216- "The pointer has left `nick`'s face — which is not yet the end of it.
217-
218- Guarded by who is being left, so the leaving of one face cannot take down
219- the card of the next one: both edges arrive in the same frame when the
220- pointer crosses straight over."
221- [nick]
222- (io/after! grace-ms #(release! nick)))
223-
224-(defn enter-dialog!
225- "The pointer is on the dialog. Whatever hover put it there is now this."
226- []
227- (reset! over-dialog? true))
228-
229-(defn leave-dialog!
230- "The pointer has left the dialog, and with it the last thing holding the
231- profile open — unless it went back to the face it came from."
155+(defn close!
156+ "Put the profile away."
232157 []
233- (reset! over-dialog? false)
234- (let [nick (:nick @hovering)]
235- (io/after! grace-ms #(release! nick))))
158+ (reset! viewing nil))
236159
237160 (defn web-url
238161 "Their profile on the web, by handle where there is one and DID otherwise."
@@ -11,22 +11,22 @@
11 them the screen is the nick and a line saying so, which is the honest answer11 them the screen is the nick and a line saying so, which is the honest answer
12 rather than a spinner that never lands.12 rather than a spinner that never lands.
13 13
14- What a pointer does about one is here too, at the foot of this file: hovering14+ One gesture opens it, on every backend: a press. A pointer resting on a face
15- a face, the grace period for crossing from the face to the card, and the card15+ used to open the card too, with a grace period for crossing from the face to
16- reporting its own pointer. That used to be jolt's alone, on the reading that16+ it and the card reporting its own pointer back, so that leaving the face was
17- a pointer meant libcosmic — but `just flutter-desktop` is a window with a17+ not the end of it. That is gone. It made the card something that could arrive
18- mouse in it as much as `just cosmic run` is, and the machine is a few atoms18+ and leave without being asked for crossing a column of faces on the way to
19- and a timer with nothing host-shaped in it. The timer is the one thing that was,19+ the scrollbar flickered one open per row and it bought a second way in at
20- and `frq.io/after!` is where that went. Whether there is a pointer at all is20+ the price of a dialog that had to be non-modal to be able to close itself.
21- still the host's answer: `actions/desktop?`.21+ Pressing a face is the whole of it now, and what it opens stays until it is
22+ closed.
22 23
23 The fetch is a seam, because it is the one part that differs a future and a24 The fetch is a seam, because it is the one part that differs a future and a
24 blocking request on one side, an awaited one on the other. Nothing is fetched25 blocking request on one side, an awaited one on the other. Nothing is fetched
25 until a backend installs one, and `entry` simply answers nil."26 until a backend installs one, and `entry` simply answers nil."
26 (:require [clojure.string :as str]27 (:require [clojure.string :as str]
27 [frq.atproto.core :as atproto]28 [frq.atproto.core :as atproto]
28- [frq.cells :as cells]29+ [frq.cells :as cells]))
29- [frq.io :as io]))
30 30
31 (def directory-host atproto/directory-host)31 (def directory-host atproto/directory-host)
32 32
@@ -37,7 +37,6 @@
37 (defonce ^:private cache (atom {}))37 (defonce ^:private cache (atom {}))
38 38
39 (def viewing cells/profile-viewing)39 (def viewing cells/profile-viewing)
40-(def hovering cells/profile-hovering)
41 (def tick cells/profile-tick)40 (def tick cells/profile-tick)
42 41
43 ;; A handle is a domain: labels joined by dots, ending in something alphabetic.42 ;; A handle is a domain: labels joined by dots, ending in something alphabetic.
@@ -153,86 +152,10 @@
153 (reset! viewing {:nick nick :actor actor})152 (reset! viewing {:nick nick :actor actor})
154 (fetch! actor))153 (fetch! actor))
155 154
156-(defn close! [] (reset! viewing nil))155+(defn close!
157-156+ "Put the profile away."
158-;; ------------------------------------------------------- what a pointer does
159-
160-;; Whether the pointer is on the dialog the hover put up.
161-;;
162-;; This is what makes a hovered profile something you can move into and read
163-;; rather than something you can only glance at: the dialog reports its own
164-;; pointer, so leaving the face is not the end of the hover if the pointer
165-;; turned up here instead.
166-(defonce ^:private over-dialog? (atom false))
167-
168-(defn dismiss!
169- "Put the profile away, however it was opened.
170-
171- The dialog is shown for `viewing` or for `hovering`, so a Close that cleared
172- only the first left one the pointer had opened on screen with its own button
173- doing nothing to it."
174- []
175- (reset! viewing nil)
176- (reset! hovering nil)
177- ;; And the pointer's claim on it. Close takes the dialog out from under the
178- ;; pointer, so there is no leaving edge coming to say so — left set, it
179- ;; would hold the next hover open for good.
180- (reset! over-dialog? false))
181-
182-;; How long the pointer may be on neither the face nor the dialog before the
183-;; dialog goes.
184-;;
185-;; There is a gap between the two — the dialog is centred and the face is
186-;; wherever the message is — and a hover that ended the instant the pointer
187-;; left the face would close it halfway across every time. Long enough to
188-;; cross, short enough that a pointer moving somewhere else entirely does not
189-;; drag it along.
190-(def ^:private grace-ms 400)
191-
192-(defn- release!
193- "Let `nick`'s hover go, unless something has taken it up again.
194-
195- Three things can have happened in the grace period: the pointer arrived on
196- the dialog, it went back to the face, or it landed on someone else's. In all
197- three there is a hover to keep, and it is not this one's to end — which is
198- what the nick guard says."
199- [nick]
200- (when-not @over-dialog?
201- (swap! hovering #(when-not (= nick (:nick %)) %))))
202-
203-(defn hover!
204- "The pointer has come to rest on someone's face. Starts the fetch opening
205- them would, so the card has something on it by the time it is read.
206-
207- Their picture is not asked for here. It is the one part of this that needs
208- the host the desktop downloads a file and the phone hands the CDN URL
209- straight to the widget so each backend asks for it at the seam, beside the
210- call to this."
211- [nick actor]
212- (reset! hovering {:nick nick :actor actor})
213- (fetch! actor))
214-
215-(defn unhover!
216- "The pointer has left `nick`'s face — which is not yet the end of it.
217-
218- Guarded by who is being left, so the leaving of one face cannot take down
219- the card of the next one: both edges arrive in the same frame when the
220- pointer crosses straight over."
221- [nick]
222- (io/after! grace-ms #(release! nick)))
223-
224-(defn enter-dialog!
225- "The pointer is on the dialog. Whatever hover put it there is now this."
226- []
227- (reset! over-dialog? true))
228-
229-(defn leave-dialog!
230- "The pointer has left the dialog, and with it the last thing holding the
231- profile open — unless it went back to the face it came from."
232 []157 []
233- (reset! over-dialog? false)158+ (reset! viewing nil))
234- (let [nick (:nick @hovering)]
235- (io/after! grace-ms #(release! nick))))
236 159
237 (defn web-url160 (defn web-url
238 "Their profile on the web, by handle where there is one and DID otherwise."161 "Their profile on the web, by handle where there is one and DID otherwise."
modified common/frq/screens/app.cljc +15 -32
@@ -149,33 +149,22 @@
149149 opens is a dialog that moves under the pointer, and the fetch lands whenever
150150 it lands.
151151
152- Who it is about comes from either of two places, and that with the
153- modality below is the whole of what hovering and pressing a face do
154- differently. Resting on one sets `hovering`, which the pointer takes away
155- again when it leaves; pressing one sets `viewing`, which nothing takes away
156- but Close. `viewing` is read first, so a pinned profile is not swapped out
157- from under the reader by a face the pointer crosses on the way to it.
158-
159- And a dialog the pointer is holding open is not modal. A modal one makes
160- the window underneath it deaf libcosmic wraps the app in a popover that
161- hands its content an `Unavailable` cursor while a popup is up, and Flutter's
162- side of this puts a scrim over the screens that absorbs what lands on it — so
163- the face that opened it never hears the pointer leave, and what a hover opened
164- could never close itself. Non-modal, the face keeps hearing, and moving away
165- shuts it. A pinned one is modal, which is what being pinned means: it is the
166- thing on the screen until it is dismissed."
152+ Who it is about is `viewing`, and there is nothing else it could be: a press
153+ sets it and only Close clears it. So the dialog is modal, which is what it
154+ always wanted to be — the thing on the screen until it is closed. It was
155+ modal only some of the time while a hovered face could raise it too: a modal
156+ dialog makes the window underneath it deaf, and the face that opened one
157+ would then never hear the pointer leave to close it again. See `frq.profile`
158+ for why that second way in is gone."
167159 []
168- (let [pinned? (some? (actions/viewing))
169- {:keys [nick actor]} (or (actions/viewing) (actions/hovering))
160+ (let [{:keys [nick actor]} (actions/viewing)
170161 _ (actions/profile-tick)
171162 _ (actions/media-tick)
172163 pr (actions/profile-entry actor)
173164 ready? (= :ready (:status pr))
174165 display (or (:display-name pr) nick)
175166 url (when ready? (actions/profile-web-url pr))]
176- [:dialog {:label display :max-width 520 :modal pinned?
177- :on-hover actions/profile-enter-dialog!
178- :on-unhover actions/profile-leave-dialog!}
167+ [:dialog {:label display :max-width 520 :modal true}
179168 ;; The body is the screen's card without its heading: the dialog's own
180169 ;; title is the name now, so repeating it under the picture is a line
181170 ;; that says nothing.
@@ -215,15 +204,9 @@
215204 ;; `slot` is where libcosmic puts a button: the two actions go to the
216205 ;; foot of the dialog, and anything else here would be another control
217206 ;; stacked in the body.
218- ;;
219- ;; Both are here whether the profile is pinned or only hovered. They were
220- ;; hidden while hovering, back when a hover could not be walked into: the
221- ;; dialog reports its own pointer now, so moving towards a button in it
222- ;; keeps it open instead of closing it, and a button you can reach is a
223- ;; button worth drawing.
224207 [:button {:key :close :slot "primary"
225- :label (if pinned? "Close" "Dismiss")
226- :on-click actions/profile-dismiss!}]
208+ :label "Close"
209+ :on-click actions/profile-close!}]
227210 [:button {:key :web :slot "secondary" :label "Bluesky ↗"
228211 :sensitive (boolean url)
229212 :on-click #(when url (actions/open-url! url))}]]))
@@ -388,13 +371,13 @@
388371 ;; paint its contents inline at the bottom of the screen. So there it stays
389372 ;; a screen you go to and come back from, which is `profile-screen`.
390373 [:vbox {:key :root :fill-height true}
391- ;; One dialog at a time, and a pinned profile outranks both pointers: it is
392- ;; the only one of the three that was asked for by a press rather than by
393- ;; where the pointer happens to be resting.
374+ ;; One dialog at a time, and the profile outranks the reaction card: it is
375+ ;; the one of the two that was asked for by a press rather than by where the
376+ ;; pointer happens to be resting.
394377 [:vbox {:key :dialog}
395378 (when-not @terminal?
396379 (cond
397- (or (actions/viewing) (actions/hovering)) [profile-dialog]
380+ (actions/viewing) [profile-dialog]
398381 @cells/reaction-hover [reactor-dialog]))]
399382 (cond
400383 @cells/lightbox [:vbox {:key :screen-lightbox} [lightbox-screen]]
@@ -149,33 +149,22 @@
149 opens is a dialog that moves under the pointer, and the fetch lands whenever149 opens is a dialog that moves under the pointer, and the fetch lands whenever
150 it lands.150 it lands.
151 151
152- Who it is about comes from either of two places, and that with the152+ Who it is about is `viewing`, and there is nothing else it could be: a press
153- modality below is the whole of what hovering and pressing a face do153+ sets it and only Close clears it. So the dialog is modal, which is what it
154- differently. Resting on one sets `hovering`, which the pointer takes away154+ always wanted to be — the thing on the screen until it is closed. It was
155- again when it leaves; pressing one sets `viewing`, which nothing takes away155+ modal only some of the time while a hovered face could raise it too: a modal
156- but Close. `viewing` is read first, so a pinned profile is not swapped out156+ dialog makes the window underneath it deaf, and the face that opened one
157- from under the reader by a face the pointer crosses on the way to it.157+ would then never hear the pointer leave to close it again. See `frq.profile`
158-158+ for why that second way in is gone."
159- And a dialog the pointer is holding open is not modal. A modal one makes
160- the window underneath it deaf libcosmic wraps the app in a popover that
161- hands its content an `Unavailable` cursor while a popup is up, and Flutter's
162- side of this puts a scrim over the screens that absorbs what lands on it — so
163- the face that opened it never hears the pointer leave, and what a hover opened
164- could never close itself. Non-modal, the face keeps hearing, and moving away
165- shuts it. A pinned one is modal, which is what being pinned means: it is the
166- thing on the screen until it is dismissed."
167 []159 []
168- (let [pinned? (some? (actions/viewing))160+ (let [{:keys [nick actor]} (actions/viewing)
169- {:keys [nick actor]} (or (actions/viewing) (actions/hovering))
170 _ (actions/profile-tick)161 _ (actions/profile-tick)
171 _ (actions/media-tick)162 _ (actions/media-tick)
172 pr (actions/profile-entry actor)163 pr (actions/profile-entry actor)
173 ready? (= :ready (:status pr))164 ready? (= :ready (:status pr))
174 display (or (:display-name pr) nick)165 display (or (:display-name pr) nick)
175 url (when ready? (actions/profile-web-url pr))]166 url (when ready? (actions/profile-web-url pr))]
176- [:dialog {:label display :max-width 520 :modal pinned?167+ [:dialog {:label display :max-width 520 :modal true}
177- :on-hover actions/profile-enter-dialog!
178- :on-unhover actions/profile-leave-dialog!}
179 ;; The body is the screen's card without its heading: the dialog's own168 ;; The body is the screen's card without its heading: the dialog's own
180 ;; title is the name now, so repeating it under the picture is a line169 ;; title is the name now, so repeating it under the picture is a line
181 ;; that says nothing.170 ;; that says nothing.
@@ -215,15 +204,9 @@
215 ;; `slot` is where libcosmic puts a button: the two actions go to the204 ;; `slot` is where libcosmic puts a button: the two actions go to the
216 ;; foot of the dialog, and anything else here would be another control205 ;; foot of the dialog, and anything else here would be another control
217 ;; stacked in the body.206 ;; stacked in the body.
218- ;;
219- ;; Both are here whether the profile is pinned or only hovered. They were
220- ;; hidden while hovering, back when a hover could not be walked into: the
221- ;; dialog reports its own pointer now, so moving towards a button in it
222- ;; keeps it open instead of closing it, and a button you can reach is a
223- ;; button worth drawing.
224 [:button {:key :close :slot "primary"207 [:button {:key :close :slot "primary"
225- :label (if pinned? "Close" "Dismiss")208+ :label "Close"
226- :on-click actions/profile-dismiss!}]209+ :on-click actions/profile-close!}]
227 [:button {:key :web :slot "secondary" :label "Bluesky ↗"210 [:button {:key :web :slot "secondary" :label "Bluesky ↗"
228 :sensitive (boolean url)211 :sensitive (boolean url)
229 :on-click #(when url (actions/open-url! url))}]]))212 :on-click #(when url (actions/open-url! url))}]]))
@@ -388,13 +371,13 @@
388 ;; paint its contents inline at the bottom of the screen. So there it stays371 ;; paint its contents inline at the bottom of the screen. So there it stays
389 ;; a screen you go to and come back from, which is `profile-screen`.372 ;; a screen you go to and come back from, which is `profile-screen`.
390 [:vbox {:key :root :fill-height true}373 [:vbox {:key :root :fill-height true}
391- ;; One dialog at a time, and a pinned profile outranks both pointers: it is374+ ;; One dialog at a time, and the profile outranks the reaction card: it is
392- ;; the only one of the three that was asked for by a press rather than by375+ ;; the one of the two that was asked for by a press rather than by where the
393- ;; where the pointer happens to be resting.376+ ;; pointer happens to be resting.
394 [:vbox {:key :dialog}377 [:vbox {:key :dialog}
395 (when-not @terminal?378 (when-not @terminal?
396 (cond379 (cond
397- (or (actions/viewing) (actions/hovering)) [profile-dialog]380+ (actions/viewing) [profile-dialog]
398 @cells/reaction-hover [reactor-dialog]))]381 @cells/reaction-hover [reactor-dialog]))]
399 (cond382 (cond
400 @cells/lightbox [:vbox {:key :screen-lightbox} [lightbox-screen]]383 @cells/lightbox [:vbox {:key :screen-lightbox} [lightbox-screen]]
modified common/frq/screens/chat.cljc +9 -14
@@ -735,9 +735,6 @@
735735 ;; The face is also the way to the person behind it: Vidya's plain
736736 ;; label does not answer the pointer, so the tap sleek puts on the
737737 ;; nick lives here, on the one thing in the row that does.
738- ;; And, where there is a pointer to ask with, resting on the face
739- ;; answers before the click does: the card under it is the profile
740- ;; screen's first few lines, painted beside the pointer.
741738 ;; And no face on this line in a terminal: the initial that stands in
742739 ;; for a portrait in a window is a letter printed twice in a cell grid,
743740 ;; which only pushed every nick in past the words it heads. Where the
@@ -745,17 +742,15 @@
745742 ;; the whole message rather than off its heading — `message-row` has it.
746743 (when-not @terminal?
747744 (let [src (actions/avatar-path (:actor m))]
748- ;; One profile, two gestures, and no card hung under the face: the
749- ;; pointer opens the dialog and the press pins it. What makes that
750- ;; work is the dialog being non-modal while the pointer is what is
751- ;; holding it open — see `profile-dialog`.
752- [:avatar (cond-> {:label (:from m)
753- :src (or src "")
754- :size face-size
755- :on-click #(actions/profile-open! (:from m) (:actor m))}
756- (actions/desktop?)
757- (assoc :on-hover #(actions/profile-hover! (:from m) (:actor m))
758- :on-unhover #(actions/profile-unhover! (:from m))))]))
745+ ;; One profile and one gesture: the press opens the dialog, on a
746+ ;; phone and in a window alike. Resting a pointer on the face used
747+ ;; to open it too, which made the card something that arrived
748+ ;; unasked for every face crossed on the way somewhere else — see
749+ ;; `frq.profile`.
750+ [:avatar {:label (:from m)
751+ :src (or src "")
752+ :size face-size
753+ :on-click #(actions/profile-open! (:from m) (:actor m))}]))
759754 ;; The name carries the row, so it is set at body size in the plain
760755 ;; text colour: dimmed caption made the one thing you scan a column
761756 ;; for the faintest thing on it.
@@ -735,9 +735,6 @@
735 ;; The face is also the way to the person behind it: Vidya's plain735 ;; The face is also the way to the person behind it: Vidya's plain
736 ;; label does not answer the pointer, so the tap sleek puts on the736 ;; label does not answer the pointer, so the tap sleek puts on the
737 ;; nick lives here, on the one thing in the row that does.737 ;; nick lives here, on the one thing in the row that does.
738- ;; And, where there is a pointer to ask with, resting on the face
739- ;; answers before the click does: the card under it is the profile
740- ;; screen's first few lines, painted beside the pointer.
741 ;; And no face on this line in a terminal: the initial that stands in738 ;; And no face on this line in a terminal: the initial that stands in
742 ;; for a portrait in a window is a letter printed twice in a cell grid,739 ;; for a portrait in a window is a letter printed twice in a cell grid,
743 ;; which only pushed every nick in past the words it heads. Where the740 ;; which only pushed every nick in past the words it heads. Where the
@@ -745,17 +742,15 @@
745 ;; the whole message rather than off its heading — `message-row` has it.742 ;; the whole message rather than off its heading — `message-row` has it.
746 (when-not @terminal?743 (when-not @terminal?
747 (let [src (actions/avatar-path (:actor m))]744 (let [src (actions/avatar-path (:actor m))]
748- ;; One profile, two gestures, and no card hung under the face: the745+ ;; One profile and one gesture: the press opens the dialog, on a
749- ;; pointer opens the dialog and the press pins it. What makes that746+ ;; phone and in a window alike. Resting a pointer on the face used
750- ;; work is the dialog being non-modal while the pointer is what is747+ ;; to open it too, which made the card something that arrived
751- ;; holding it open — see `profile-dialog`.748+ ;; unasked for every face crossed on the way somewhere else — see
752- [:avatar (cond-> {:label (:from m)749+ ;; `frq.profile`.
753- :src (or src "")750+ [:avatar {:label (:from m)
754- :size face-size751+ :src (or src "")
755- :on-click #(actions/profile-open! (:from m) (:actor m))}752+ :size face-size
756- (actions/desktop?)753+ :on-click #(actions/profile-open! (:from m) (:actor m))}]))
757- (assoc :on-hover #(actions/profile-hover! (:from m) (:actor m))
758- :on-unhover #(actions/profile-unhover! (:from m))))]))
759 ;; The name carries the row, so it is set at body size in the plain754 ;; The name carries the row, so it is set at body size in the plain
760 ;; text colour: dimmed caption made the one thing you scan a column755 ;; text colour: dimmed caption made the one thing you scan a column
761 ;; for the faintest thing on it.756 ;; for the faintest thing on it.
modified flutter/src/frq/hiccup.cljd +62 -73
@@ -873,14 +873,12 @@
873873 ;; On a phone there is no behind worth keeping the screen is the width
874874 ;; of the dialog already so this is a sheet: the title, a rule, and
875875 ;; the body, drawn where the tree puts it, above the conversation it was
876- ;; opened from. `:modal` and the hover pair are read and ignored, since
877- ;; they are what a pointer needs to tell a card it opened by resting
878- ;; from one it opened by pressing, and a finger only ever presses.
876+ ;; opened from. `:modal` is read and ignored: a sheet the width of the
877+ ;; screen has nothing behind it to make deaf.
879878 ;;
880879 ;; In a window it is what libcosmic's is: capped at `:max-width`,
881- ;; centred over the screen, the buttons gathered into a foot, and the
882- ;; hover pair heard — so a card a pointer raised can be moved into and
883- ;; read rather than only glanced at. Nothing about the floating is here,
880+ ;; centred over the screen, and the buttons gathered into a foot.
881+ ;; Nothing about the floating is here,
884882 ;; though; a widget cannot lift itself out of the column it is in. That
885883 ;; is `render-root`, which takes the dialog out of the tree and stacks
886884 ;; it over what is left. This arm only has to draw the card.
@@ -910,59 +908,57 @@
910908 ;; one nearest the corner the pointer leaves by.
911909 foot (concat (remove #(= "primary" (:slot (props %))) foot)
912910 (filter #(= "primary" (:slot (props %))) foot))]
913- (hovered
914- p
915- (m/ConstrainedBox
916- ;; Capped both ways. The width is what libcosmic is asked for; the
917- ;; height is so that a long bio is a card with a scroll in it
918- ;; rather than a card taller than the window. The body is the part
919- ;; that gives — the title and the buttons are why it is a dialog.
920- .constraints (m/BoxConstraints
921- .maxWidth (dbl (:max-width p) 520.0)
922- .maxHeight (* 0.8 (.-height (.-size (m/MediaQuery.of ctx)))))
911+ (m/ConstrainedBox
912+ ;; Capped both ways. The width is what libcosmic is asked for; the
913+ ;; height is so that a long bio is a card with a scroll in it
914+ ;; rather than a card taller than the window. The body is the part
915+ ;; that gives — the title and the buttons are why it is a dialog.
916+ .constraints (m/BoxConstraints
917+ .maxWidth (dbl (:max-width p) 520.0)
918+ .maxHeight (* 0.8 (.-height (.-size (m/MediaQuery.of ctx)))))
919+ .child
920+ (m/Container
921+ ;; Infinity under a maxWidth of 520 is 520, and under a narrower
922+ ;; window it is the window: the card is as wide as it is allowed
923+ ;; to be, never as wide as its longest line. Without it the
924+ ;; Column is sized to its children and the rules inside it ask
925+ ;; for an unbounded width, which is a layout error rather than a
926+ ;; narrow dialog.
927+ .width double/infinity
928+ .padding (m/EdgeInsets.all t/space-s)
929+ .decoration (m/BoxDecoration
930+ .color t/card
931+ .border (m/Border.all .color t/divider .width 1.0)
932+ .borderRadius (m/BorderRadius.circular t/radius-m))
923933 .child
924- (m/Container
925- ;; Infinity under a maxWidth of 520 is 520, and under a narrower
926- ;; window it is the window: the card is as wide as it is allowed
927- ;; to be, never as wide as its longest line. Without it the
928- ;; Column is sized to its children and the rules inside it ask
929- ;; for an unbounded width, which is a layout error rather than a
930- ;; narrow dialog.
931- .width double/infinity
932- .padding (m/EdgeInsets.all t/space-s)
933- .decoration (m/BoxDecoration
934- .color t/card
935- .border (m/Border.all .color t/divider .width 1.0)
936- .borderRadius (m/BorderRadius.circular t/radius-m))
937- .child
938- ;; Built out rather than handed to `col`, because the foot is a
939- ;; Row aligned to the end and `:hbox` has no word for that. One
940- ;; place that wants it and no prop invented for it.
941- (m/Column
942- .crossAxisAlignment m/CrossAxisAlignment.start
943- .mainAxisSize m/MainAxisSize.min
944- .spacing t/space-xxs
945- .children
946- (concat [(render [:title-2 {:label (:label p "")}])
947- (render [:separator {}])]
948- ;; Flexible and loose: the body takes what it needs up
949- ;; to what the cap leaves, and scrolls inside that. A
950- ;; short profile is still a short card.
951- [(m/Flexible
952- .child
953- (m/SingleChildScrollView
954- .child (m/Column
955- .crossAxisAlignment m/CrossAxisAlignment.start
956- .mainAxisSize m/MainAxisSize.min
957- .spacing t/space-xxs
958- .children (children (remove slotted kids)))))]
959- (when (seq foot)
960- [(render [:separator {}])
961- (m/Row
962- .mainAxisSize m/MainAxisSize.max
963- .mainAxisAlignment m/MainAxisAlignment.end
964- .spacing t/space-xxs
965- .children (children foot))]))))))))
934+ ;; Built out rather than handed to `col`, because the foot is a
935+ ;; Row aligned to the end and `:hbox` has no word for that. One
936+ ;; place that wants it and no prop invented for it.
937+ (m/Column
938+ .crossAxisAlignment m/CrossAxisAlignment.start
939+ .mainAxisSize m/MainAxisSize.min
940+ .spacing t/space-xxs
941+ .children
942+ (concat [(render [:title-2 {:label (:label p "")}])
943+ (render [:separator {}])]
944+ ;; Flexible and loose: the body takes what it needs up
945+ ;; to what the cap leaves, and scrolls inside that. A
946+ ;; short profile is still a short card.
947+ [(m/Flexible
948+ .child
949+ (m/SingleChildScrollView
950+ .child (m/Column
951+ .crossAxisAlignment m/CrossAxisAlignment.start
952+ .mainAxisSize m/MainAxisSize.min
953+ .spacing t/space-xxs
954+ .children (children (remove slotted kids)))))]
955+ (when (seq foot)
956+ [(render [:separator {}])
957+ (m/Row
958+ .mainAxisSize m/MainAxisSize.max
959+ .mainAxisAlignment m/MainAxisAlignment.end
960+ .spacing t/space-xxs
961+ .children (children foot))])))))))
966962
967963 :separator (m/Divider .height 1.0 .thickness 1.0 .color t/divider)
968964
@@ -1074,14 +1070,9 @@
10741070 (m/Text (str (:emoji p "")) .style (t/emoji-style (dbl (:size p) 16.0)))
10751071
10761072 ;; A face is a way in to who someone is, so it takes the press that
1077- ;; opens their profile. It was drawn without one and the chat screen's
1078- ;; `:on-click` went nowhere the one gesture a phone has for this.
1079- ;;
1080- ;; And, in a window, the gesture that comes before the press: resting on
1081- ;; a face raises the card without pinning it. `hovered` is what hears
1082- ;; that; the card it raises is the `:dialog` arm above, floated by
1083- ;; `render-root`. On the phone the screens do not even hang a hover on a
1084- ;; face they ask `desktop?` first and `hovered` is a no-op besides.
1073+ ;; opens their profile the one gesture a phone has for this, and the
1074+ ;; only one either target uses. It was drawn without one and the chat
1075+ ;; screen's `:on-click` went nowhere.
10851076 :avatar
10861077 (let [s (dbl (:size p) 32.0)
10871078 src (:src p)
@@ -1111,13 +1102,11 @@
11111102 (.toUpperCase (subs l 0 1))
11121103 "?"))
11131104 t/text-body t/on-bg)))]
1114- (hovered
1115- p
1116- (if-let [on (:on-click p)]
1117- (m/InkWell .onTap #(on)
1118- .customBorder (m/CircleBorder)
1119- .child face)
1120- face)))
1105+ (if-let [on (:on-click p)]
1106+ (m/InkWell .onTap #(on)
1107+ .customBorder (m/CircleBorder)
1108+ .child face)
1109+ face))
11211110
11221111 :image
11231112 (let [src (or (:src p) (:path p))
@@ -873,14 +873,12 @@
873 ;; On a phone there is no behind worth keeping the screen is the width873 ;; On a phone there is no behind worth keeping the screen is the width
874 ;; of the dialog already so this is a sheet: the title, a rule, and874 ;; of the dialog already so this is a sheet: the title, a rule, and
875 ;; the body, drawn where the tree puts it, above the conversation it was875 ;; the body, drawn where the tree puts it, above the conversation it was
876- ;; opened from. `:modal` and the hover pair are read and ignored, since876+ ;; opened from. `:modal` is read and ignored: a sheet the width of the
877- ;; they are what a pointer needs to tell a card it opened by resting877+ ;; screen has nothing behind it to make deaf.
878- ;; from one it opened by pressing, and a finger only ever presses.
879 ;;878 ;;
880 ;; In a window it is what libcosmic's is: capped at `:max-width`,879 ;; In a window it is what libcosmic's is: capped at `:max-width`,
881- ;; centred over the screen, the buttons gathered into a foot, and the880+ ;; centred over the screen, and the buttons gathered into a foot.
882- ;; hover pair heard — so a card a pointer raised can be moved into and881+ ;; Nothing about the floating is here,
883- ;; read rather than only glanced at. Nothing about the floating is here,
884 ;; though; a widget cannot lift itself out of the column it is in. That882 ;; though; a widget cannot lift itself out of the column it is in. That
885 ;; is `render-root`, which takes the dialog out of the tree and stacks883 ;; is `render-root`, which takes the dialog out of the tree and stacks
886 ;; it over what is left. This arm only has to draw the card.884 ;; it over what is left. This arm only has to draw the card.
@@ -910,59 +908,57 @@
910 ;; one nearest the corner the pointer leaves by.908 ;; one nearest the corner the pointer leaves by.
911 foot (concat (remove #(= "primary" (:slot (props %))) foot)909 foot (concat (remove #(= "primary" (:slot (props %))) foot)
912 (filter #(= "primary" (:slot (props %))) foot))]910 (filter #(= "primary" (:slot (props %))) foot))]
913- (hovered911+ (m/ConstrainedBox
914- p912+ ;; Capped both ways. The width is what libcosmic is asked for; the
915- (m/ConstrainedBox913+ ;; height is so that a long bio is a card with a scroll in it
916- ;; Capped both ways. The width is what libcosmic is asked for; the914+ ;; rather than a card taller than the window. The body is the part
917- ;; height is so that a long bio is a card with a scroll in it915+ ;; that gives — the title and the buttons are why it is a dialog.
918- ;; rather than a card taller than the window. The body is the part916+ .constraints (m/BoxConstraints
919- ;; that gives — the title and the buttons are why it is a dialog.917+ .maxWidth (dbl (:max-width p) 520.0)
920- .constraints (m/BoxConstraints918+ .maxHeight (* 0.8 (.-height (.-size (m/MediaQuery.of ctx)))))
921- .maxWidth (dbl (:max-width p) 520.0)919+ .child
922- .maxHeight (* 0.8 (.-height (.-size (m/MediaQuery.of ctx)))))920+ (m/Container
921+ ;; Infinity under a maxWidth of 520 is 520, and under a narrower
922+ ;; window it is the window: the card is as wide as it is allowed
923+ ;; to be, never as wide as its longest line. Without it the
924+ ;; Column is sized to its children and the rules inside it ask
925+ ;; for an unbounded width, which is a layout error rather than a
926+ ;; narrow dialog.
927+ .width double/infinity
928+ .padding (m/EdgeInsets.all t/space-s)
929+ .decoration (m/BoxDecoration
930+ .color t/card
931+ .border (m/Border.all .color t/divider .width 1.0)
932+ .borderRadius (m/BorderRadius.circular t/radius-m))
923 .child933 .child
924- (m/Container934+ ;; Built out rather than handed to `col`, because the foot is a
925- ;; Infinity under a maxWidth of 520 is 520, and under a narrower935+ ;; Row aligned to the end and `:hbox` has no word for that. One
926- ;; window it is the window: the card is as wide as it is allowed936+ ;; place that wants it and no prop invented for it.
927- ;; to be, never as wide as its longest line. Without it the937+ (m/Column
928- ;; Column is sized to its children and the rules inside it ask938+ .crossAxisAlignment m/CrossAxisAlignment.start
929- ;; for an unbounded width, which is a layout error rather than a939+ .mainAxisSize m/MainAxisSize.min
930- ;; narrow dialog.940+ .spacing t/space-xxs
931- .width double/infinity941+ .children
932- .padding (m/EdgeInsets.all t/space-s)942+ (concat [(render [:title-2 {:label (:label p "")}])
933- .decoration (m/BoxDecoration943+ (render [:separator {}])]
934- .color t/card944+ ;; Flexible and loose: the body takes what it needs up
935- .border (m/Border.all .color t/divider .width 1.0)945+ ;; to what the cap leaves, and scrolls inside that. A
936- .borderRadius (m/BorderRadius.circular t/radius-m))946+ ;; short profile is still a short card.
937- .child947+ [(m/Flexible
938- ;; Built out rather than handed to `col`, because the foot is a948+ .child
939- ;; Row aligned to the end and `:hbox` has no word for that. One949+ (m/SingleChildScrollView
940- ;; place that wants it and no prop invented for it.950+ .child (m/Column
941- (m/Column951+ .crossAxisAlignment m/CrossAxisAlignment.start
942- .crossAxisAlignment m/CrossAxisAlignment.start952+ .mainAxisSize m/MainAxisSize.min
943- .mainAxisSize m/MainAxisSize.min953+ .spacing t/space-xxs
944- .spacing t/space-xxs954+ .children (children (remove slotted kids)))))]
945- .children955+ (when (seq foot)
946- (concat [(render [:title-2 {:label (:label p "")}])956+ [(render [:separator {}])
947- (render [:separator {}])]957+ (m/Row
948- ;; Flexible and loose: the body takes what it needs up958+ .mainAxisSize m/MainAxisSize.max
949- ;; to what the cap leaves, and scrolls inside that. A959+ .mainAxisAlignment m/MainAxisAlignment.end
950- ;; short profile is still a short card.960+ .spacing t/space-xxs
951- [(m/Flexible961+ .children (children foot))])))))))
952- .child
953- (m/SingleChildScrollView
954- .child (m/Column
955- .crossAxisAlignment m/CrossAxisAlignment.start
956- .mainAxisSize m/MainAxisSize.min
957- .spacing t/space-xxs
958- .children (children (remove slotted kids)))))]
959- (when (seq foot)
960- [(render [:separator {}])
961- (m/Row
962- .mainAxisSize m/MainAxisSize.max
963- .mainAxisAlignment m/MainAxisAlignment.end
964- .spacing t/space-xxs
965- .children (children foot))]))))))))
966 962
967 :separator (m/Divider .height 1.0 .thickness 1.0 .color t/divider)963 :separator (m/Divider .height 1.0 .thickness 1.0 .color t/divider)
968 964
@@ -1074,14 +1070,9 @@
1074 (m/Text (str (:emoji p "")) .style (t/emoji-style (dbl (:size p) 16.0)))1070 (m/Text (str (:emoji p "")) .style (t/emoji-style (dbl (:size p) 16.0)))
1075 1071
1076 ;; A face is a way in to who someone is, so it takes the press that1072 ;; A face is a way in to who someone is, so it takes the press that
1077- ;; opens their profile. It was drawn without one and the chat screen's1073+ ;; opens their profile the one gesture a phone has for this, and the
1078- ;; `:on-click` went nowhere the one gesture a phone has for this.1074+ ;; only one either target uses. It was drawn without one and the chat
1079- ;;1075+ ;; screen's `:on-click` went nowhere.
1080- ;; And, in a window, the gesture that comes before the press: resting on
1081- ;; a face raises the card without pinning it. `hovered` is what hears
1082- ;; that; the card it raises is the `:dialog` arm above, floated by
1083- ;; `render-root`. On the phone the screens do not even hang a hover on a
1084- ;; face they ask `desktop?` first and `hovered` is a no-op besides.
1085 :avatar1076 :avatar
1086 (let [s (dbl (:size p) 32.0)1077 (let [s (dbl (:size p) 32.0)
1087 src (:src p)1078 src (:src p)
@@ -1111,13 +1102,11 @@
1111 (.toUpperCase (subs l 0 1))1102 (.toUpperCase (subs l 0 1))
1112 "?"))1103 "?"))
1113 t/text-body t/on-bg)))]1104 t/text-body t/on-bg)))]
1114- (hovered1105+ (if-let [on (:on-click p)]
1115- p1106+ (m/InkWell .onTap #(on)
1116- (if-let [on (:on-click p)]1107+ .customBorder (m/CircleBorder)
1117- (m/InkWell .onTap #(on)1108+ .child face)
1118- .customBorder (m/CircleBorder)1109+ face))
1119- .child face)
1120- face)))
1121 1110
1122 :image1111 :image
1123 (let [src (or (:src p) (:path p))1112 (let [src (or (:src p) (:path p))
modified flutter/src/frq/io/dart.cljd +1 -6
@@ -176,9 +176,4 @@
176176 :utf8-string utf8-string
177177 :wall-nanos (fn [] (* 1000 (.-microsecondsSinceEpoch (DateTime/now))))
178178 :mono-nanos (fn [] (* 1000 (.-inMicroseconds (.-elapsed ^Stopwatch uptime))))
179- :local-offset-seconds local-offset-seconds
180- ;; Dart has one event loop and the UI runs on it, so there is no thread to
181- ;; hand the callback back to — a timer is the whole of it.
182- :after! (fn [ms f]
183- (Future.delayed (Duration .milliseconds (int ms)) f)
184- nil)}))
179+ :local-offset-seconds local-offset-seconds}))
@@ -176,9 +176,4 @@
176 :utf8-string utf8-string176 :utf8-string utf8-string
177 :wall-nanos (fn [] (* 1000 (.-microsecondsSinceEpoch (DateTime/now))))177 :wall-nanos (fn [] (* 1000 (.-microsecondsSinceEpoch (DateTime/now))))
178 :mono-nanos (fn [] (* 1000 (.-inMicroseconds (.-elapsed ^Stopwatch uptime))))178 :mono-nanos (fn [] (* 1000 (.-inMicroseconds (.-elapsed ^Stopwatch uptime))))
179- :local-offset-seconds local-offset-seconds179+ :local-offset-seconds local-offset-seconds}))
180- ;; Dart has one event loop and the UI runs on it, so there is no thread to
181- ;; hand the callback back to — a timer is the whole of it.
182- :after! (fn [ms f]
183- (Future.delayed (Duration .milliseconds (int ms)) f)
184- nil)}))
modified flutter/src/frq/main.cljd +0 -16
@@ -1176,28 +1176,12 @@
11761176 (avatars/fetch! actor bump!)
11771177 (profile/open! nick actor))
11781178 :profile-close! profile/close!
1179- ;; `dismiss!` and not `close!`: the card is shown for `viewing` or for
1180- ;; `hovering`, and a Close that cleared only the first leaves the one a
1181- ;; pointer opened on screen with its own button doing nothing.
1182- :profile-dismiss! profile/dismiss!
11831179 :profile-entry profile/entry
11841180 :profile-tick (fn [] @profile/tick)
11851181 :profile-stats-line profile/stats-line
11861182 :profile-truncate profile/truncate
11871183 :profile-web-url profile/web-url
11881184 :viewing (fn [] @profile/viewing)
1189- ;; The pointer half of a profile. These were no-ops while this file was
1190- ;; only ever an APK a finger is either on a face or not on it and
1191- ;; `just flutter-desktop` has a mouse, so the machine under them is the
1192- ;; shared one now. Nothing reaches them on the phone anyway: the screens
1193- ;; ask `desktop?` before they hang a hover on a face at all.
1194- :hovering (fn [] @profile/hovering)
1195- :profile-hover! (fn [nick actor]
1196- (avatars/fetch! actor bump!)
1197- (profile/hover! nick actor))
1198- :profile-unhover! profile/unhover!
1199- :profile-enter-dialog! profile/enter-dialog!
1200- :profile-leave-dialog! profile/leave-dialog!
12011185 ;; Their profile on the web, handed to the browser the same way the
12021186 ;; sign-in link is.
12031187 :open-url! (fn [url] (fio/open-url! url))
@@ -1176,28 +1176,12 @@
1176 (avatars/fetch! actor bump!)1176 (avatars/fetch! actor bump!)
1177 (profile/open! nick actor))1177 (profile/open! nick actor))
1178 :profile-close! profile/close!1178 :profile-close! profile/close!
1179- ;; `dismiss!` and not `close!`: the card is shown for `viewing` or for
1180- ;; `hovering`, and a Close that cleared only the first leaves the one a
1181- ;; pointer opened on screen with its own button doing nothing.
1182- :profile-dismiss! profile/dismiss!
1183 :profile-entry profile/entry1179 :profile-entry profile/entry
1184 :profile-tick (fn [] @profile/tick)1180 :profile-tick (fn [] @profile/tick)
1185 :profile-stats-line profile/stats-line1181 :profile-stats-line profile/stats-line
1186 :profile-truncate profile/truncate1182 :profile-truncate profile/truncate
1187 :profile-web-url profile/web-url1183 :profile-web-url profile/web-url
1188 :viewing (fn [] @profile/viewing)1184 :viewing (fn [] @profile/viewing)
1189- ;; The pointer half of a profile. These were no-ops while this file was
1190- ;; only ever an APK a finger is either on a face or not on it and
1191- ;; `just flutter-desktop` has a mouse, so the machine under them is the
1192- ;; shared one now. Nothing reaches them on the phone anyway: the screens
1193- ;; ask `desktop?` before they hang a hover on a face at all.
1194- :hovering (fn [] @profile/hovering)
1195- :profile-hover! (fn [nick actor]
1196- (avatars/fetch! actor bump!)
1197- (profile/hover! nick actor))
1198- :profile-unhover! profile/unhover!
1199- :profile-enter-dialog! profile/enter-dialog!
1200- :profile-leave-dialog! profile/leave-dialog!
1201 ;; Their profile on the web, handed to the browser the same way the1185 ;; Their profile on the web, handed to the browser the same way the
1202 ;; sign-in link is.1186 ;; sign-in link is.
1203 :open-url! (fn [url] (fio/open-url! url))1187 :open-url! (fn [url] (fio/open-url! url))
modified src/frq/app.clj +3 -9
@@ -25,7 +25,7 @@
2525 [frq.glyphs :as glyphs]
2626 [frq.media :as media]
2727 [frq.platform :as platform]
28- [frq.profile.pointer :as profile]
28+ [frq.profile.host :as profile]
2929 ;; The connect screen lives in common/ now — the same file the
3030 ;; phone renders. It reads frq.cells and calls frq.actions, and
3131 ;; this requires it exactly where its own copy used to be.
@@ -208,18 +208,12 @@
208208 {:avatar-path (fn [actor] @(avatar-path actor))
209209 :image-path (fn [url] @(image-path url))
210210 ;; The profile half lives here rather than in frq.state, which does not
211- ;; require frq.profile — who is hovered and whose card is open is a question
212- ;; about the screen, not about the connection.
213- :hovering (fn [] @profile/hovering)
211+ ;; require frq.profile — whose card is open is a question about the screen,
212+ ;; not about the connection.
214213 :viewing (fn [] @profile/viewing)
215- :profile-hover! profile/hover!
216- :profile-unhover! profile/unhover!
217214 :profile-open! profile/open!
218215 :profile-close! profile/close!
219- :profile-dismiss! profile/dismiss!
220- :profile-enter-dialog! profile/enter-dialog!
221216 :profile-entry profile/entry
222- :profile-leave-dialog! profile/leave-dialog!
223217 :profile-stats-line profile/stats-line
224218 :profile-tick (fn [] @profile/tick)
225219 :profile-truncate profile/truncate
@@ -25,7 +25,7 @@
25 [frq.glyphs :as glyphs]25 [frq.glyphs :as glyphs]
26 [frq.media :as media]26 [frq.media :as media]
27 [frq.platform :as platform]27 [frq.platform :as platform]
28- [frq.profile.pointer :as profile]28+ [frq.profile.host :as profile]
29 ;; The connect screen lives in common/ now — the same file the29 ;; The connect screen lives in common/ now — the same file the
30 ;; phone renders. It reads frq.cells and calls frq.actions, and30 ;; phone renders. It reads frq.cells and calls frq.actions, and
31 ;; this requires it exactly where its own copy used to be.31 ;; this requires it exactly where its own copy used to be.
@@ -208,18 +208,12 @@
208 {:avatar-path (fn [actor] @(avatar-path actor))208 {:avatar-path (fn [actor] @(avatar-path actor))
209 :image-path (fn [url] @(image-path url))209 :image-path (fn [url] @(image-path url))
210 ;; The profile half lives here rather than in frq.state, which does not210 ;; The profile half lives here rather than in frq.state, which does not
211- ;; require frq.profile — who is hovered and whose card is open is a question211+ ;; require frq.profile — whose card is open is a question about the screen,
212- ;; about the screen, not about the connection.212+ ;; not about the connection.
213- :hovering (fn [] @profile/hovering)
214 :viewing (fn [] @profile/viewing)213 :viewing (fn [] @profile/viewing)
215- :profile-hover! profile/hover!
216- :profile-unhover! profile/unhover!
217 :profile-open! profile/open!214 :profile-open! profile/open!
218 :profile-close! profile/close!215 :profile-close! profile/close!
219- :profile-dismiss! profile/dismiss!
220- :profile-enter-dialog! profile/enter-dialog!
221 :profile-entry profile/entry216 :profile-entry profile/entry
222- :profile-leave-dialog! profile/leave-dialog!
223 :profile-stats-line profile/stats-line217 :profile-stats-line profile/stats-line
224 :profile-tick (fn [] @profile/tick)218 :profile-tick (fn [] @profile/tick)
225 :profile-truncate profile/truncate219 :profile-truncate profile/truncate
modified src/frq/io/jolt.clj +1 -5
@@ -118,8 +118,4 @@
118118 :utf8-string (fn [bs] (String. (byte-array (map unchecked-byte bs))))
119119 :wall-nanos host/wall-nanos
120120 :mono-nanos host/mono-nanos
121- :local-offset-seconds (fn [secs] (host/tz-offset-seconds @zone secs))
122- ;; The window's own timer and not a thread that sleeps: a callback that
123- ;; repaints has to arrive on the thread the toolkit draws from, and
124- ;; `frq.platform` is where each backend lends this one its loop.
125- :after! platform/after!})
121+ :local-offset-seconds (fn [secs] (host/tz-offset-seconds @zone secs))})
@@ -118,8 +118,4 @@
118 :utf8-string (fn [bs] (String. (byte-array (map unchecked-byte bs))))118 :utf8-string (fn [bs] (String. (byte-array (map unchecked-byte bs))))
119 :wall-nanos host/wall-nanos119 :wall-nanos host/wall-nanos
120 :mono-nanos host/mono-nanos120 :mono-nanos host/mono-nanos
121- :local-offset-seconds (fn [secs] (host/tz-offset-seconds @zone secs))121+ :local-offset-seconds (fn [secs] (host/tz-offset-seconds @zone secs))})
122- ;; The window's own timer and not a thread that sleeps: a callback that
123- ;; repaints has to arrive on the thread the toolkit draws from, and
124- ;; `frq.platform` is where each backend lends this one its loop.
125- :after! platform/after!})
renamed src/frq/profile/host.clj +6 -23
similarity index 54%
rename from src/frq/profile/pointer.clj
rename to src/frq/profile/host.clj
@@ -1,18 +1,12 @@
1-(ns frq.profile.pointer
1+(ns frq.profile.host
22 "How this half fetches a profile, and the picture that goes with one.
33
44 `frq.profile` under common/ is what a profile *is* — the cache, the fields,
5- the URL, the counts — and what a pointer does about one: hovering a face, the
6- grace period for crossing to the card, the card reporting its own pointer.
7- All of that used to be here, on the reading that a pointer meant libcosmic.
8- It does not: `just flutter-desktop` is a window with a mouse in it too, and
9- the machine was a few atoms and a timer. It moved, and `frq.io/after!` took
10- the timer with it.
11-
12- What is left is the two things that genuinely differ by host. The fetch,
13- because a `future` and a blocking request is this backend's answer to a
14- question the other one answers by awaiting. And the avatar, because the
15- desktop downloads a file where the phone hands over a CDN URL.
5+ the URL, the counts, and the press that opens one. What is left here is the
6+ two things that genuinely differ by host. The fetch, because a `future` and a
7+ blocking request is this backend's answer to a question the other one answers
8+ by awaiting. And the avatar, because the desktop downloads a file where the
9+ phone hands over a CDN URL.
1610
1711 The shared names are re-exported so callers did not have to move."
1812 (:require [frq.atproto :as atproto]
@@ -20,17 +14,12 @@
2014 [frq.profile :as profile]))
2115
2216 (def viewing profile/viewing)
23-(def hovering profile/hovering)
2417 (def tick profile/tick)
2518 (def entry profile/entry)
2619 (def close! profile/close!)
27-(def dismiss! profile/dismiss!)
2820 (def web-url profile/web-url)
2921 (def stats-line profile/stats-line)
3022 (def truncate profile/truncate)
31-(def enter-dialog! profile/enter-dialog!)
32-(def leave-dialog! profile/leave-dialog!)
33-(def unhover! profile/unhover!)
3423
3524 (profile/install-fetch!
3625 (fn [actor]
@@ -54,9 +43,3 @@
5443 [nick actor]
5544 (with-avatar! actor)
5645 (profile/open! nick actor))
57-
58-(defn hover!
59- "The pointer has come to rest on someone's face."
60- [nick actor]
61- (with-avatar! actor)
62- (profile/hover! nick actor))
similarity index 54%
rename from src/frq/profile/pointer.clj
rename to src/frq/profile/host.clj
@@ -1,18 +1,12 @@
1-(ns frq.profile.pointer1+(ns frq.profile.host
2 "How this half fetches a profile, and the picture that goes with one.2 "How this half fetches a profile, and the picture that goes with one.
3 3
4 `frq.profile` under common/ is what a profile *is* — the cache, the fields,4 `frq.profile` under common/ is what a profile *is* — the cache, the fields,
5- the URL, the counts — and what a pointer does about one: hovering a face, the5+ the URL, the counts, and the press that opens one. What is left here is the
6- grace period for crossing to the card, the card reporting its own pointer.6+ two things that genuinely differ by host. The fetch, because a `future` and a
7- All of that used to be here, on the reading that a pointer meant libcosmic.7+ blocking request is this backend's answer to a question the other one answers
8- It does not: `just flutter-desktop` is a window with a mouse in it too, and8+ by awaiting. And the avatar, because the desktop downloads a file where the
9- the machine was a few atoms and a timer. It moved, and `frq.io/after!` took9+ phone hands over a CDN URL.
10- the timer with it.
11-
12- What is left is the two things that genuinely differ by host. The fetch,
13- because a `future` and a blocking request is this backend's answer to a
14- question the other one answers by awaiting. And the avatar, because the
15- desktop downloads a file where the phone hands over a CDN URL.
16 10
17 The shared names are re-exported so callers did not have to move."11 The shared names are re-exported so callers did not have to move."
18 (:require [frq.atproto :as atproto]12 (:require [frq.atproto :as atproto]
@@ -20,17 +14,12 @@
20 [frq.profile :as profile]))14 [frq.profile :as profile]))
21 15
22 (def viewing profile/viewing)16 (def viewing profile/viewing)
23-(def hovering profile/hovering)
24 (def tick profile/tick)17 (def tick profile/tick)
25 (def entry profile/entry)18 (def entry profile/entry)
26 (def close! profile/close!)19 (def close! profile/close!)
27-(def dismiss! profile/dismiss!)
28 (def web-url profile/web-url)20 (def web-url profile/web-url)
29 (def stats-line profile/stats-line)21 (def stats-line profile/stats-line)
30 (def truncate profile/truncate)22 (def truncate profile/truncate)
31-(def enter-dialog! profile/enter-dialog!)
32-(def leave-dialog! profile/leave-dialog!)
33-(def unhover! profile/unhover!)
34 23
35 (profile/install-fetch!24 (profile/install-fetch!
36 (fn [actor]25 (fn [actor]
@@ -54,9 +43,3 @@
54 [nick actor]43 [nick actor]
55 (with-avatar! actor)44 (with-avatar! actor)
56 (profile/open! nick actor))45 (profile/open! nick actor))
57-
58-(defn hover!
59- "The pointer has come to rest on someone's face."
60- [nick actor]
61- (with-avatar! actor)
62- (profile/hover! nick actor))