titlebar.ts

  1import { ColorScheme } from "../common";
  2import { icon_button, toggleable_icon_button } from "../component/icon_button"
  3import { toggleable_text_button } from "../component/text_button"
  4import { interactive, toggleable } from "../element"
  5import { withOpacity } from "../theme/color";
  6import { background, border, foreground, text } from "./components";
  7
  8const ITEM_SPACING = 8
  9const TITLEBAR_HEIGHT = 32
 10
 11function build_spacing(
 12    container_height: number,
 13    element_height: number,
 14    spacing: number
 15) {
 16    return {
 17        group: spacing,
 18        item: spacing / 2,
 19        half_item: spacing / 4,
 20        marginY: (container_height - element_height) / 2,
 21        marginX: (container_height - element_height) / 2,
 22    }
 23}
 24
 25function call_controls(theme: ColorScheme) {
 26    const button_height = 19
 27
 28    const space = build_spacing(TITLEBAR_HEIGHT, button_height, ITEM_SPACING);
 29
 30    return {
 31        toggle_microphone_button: toggleable_icon_button(theme, {
 32            margin: {
 33                left: space.group,
 34                right: space.half_item,
 35            },
 36            active_color: 'negative'
 37        }),
 38
 39        toggle_speakers_button: toggleable_icon_button(theme, {
 40            margin: {
 41                left: space.half_item,
 42                right: space.half_item
 43            },
 44        }),
 45
 46        screen_share_button: toggleable_icon_button(theme, {
 47            margin: {
 48                left: space.half_item,
 49                right: space.group
 50            },
 51            active_color: 'accent'
 52        }),
 53    }
 54}
 55
 56/**
 57* Opens the User Menu when toggled
 58*
 59* When logged in shows the user's avatar and a chevron,
 60* When logged out only shows a chevron.
 61*/
 62function user_menu(theme: ColorScheme) {
 63    const button_height = 19
 64
 65    const space = build_spacing(TITLEBAR_HEIGHT, button_height, ITEM_SPACING);
 66
 67    const build_button = ({ online }: { online: boolean }) => {
 68        const button = toggleable({
 69            base: interactive({
 70                base: {
 71                    cornerRadius: 6,
 72                    height: button_height,
 73                    width: online ? 36 : 23,
 74                    padding: {
 75                        top: 2,
 76                        bottom: 2,
 77                        left: 6,
 78                        right: 6,
 79                    },
 80                    margin: {
 81                        left: space.item,
 82                        right: space.item,
 83                    },
 84                    ...text(theme.lowest, "sans", { size: "xs" }),
 85                    background: background(theme.lowest),
 86                },
 87                state: {
 88                    hovered: {
 89                        ...text(theme.lowest, "sans", "hovered", {
 90                            size: "xs",
 91                        }),
 92                        background: background(theme.lowest, "hovered"),
 93                    },
 94                    clicked: {
 95                        ...text(theme.lowest, "sans", "pressed", {
 96                            size: "xs",
 97                        }),
 98                        background: background(theme.lowest, "pressed"),
 99                    },
100                },
101            }),
102            state: {
103                active: {
104                    default: {
105                        ...text(theme.lowest, "sans", "active", { size: "xs" }),
106                        background: background(theme.middle),
107                    },
108                    hovered: {
109                        ...text(theme.lowest, "sans", "active", { size: "xs" }),
110                        background: background(theme.middle, "hovered"),
111                    },
112                    clicked: {
113                        ...text(theme.lowest, "sans", "active", { size: "xs" }),
114                        background: background(theme.middle, "pressed"),
115                    },
116                },
117            }
118        });
119
120        return {
121            user_menu: button,
122            avatar: {
123                icon_width: 15,
124                icon_height: 15,
125                corner_radius: 4,
126                outer_width: 15,
127                outer_corner_radius: 15
128            },
129            icon: {
130                margin: {
131                    top: 2,
132                    left: online ? space.item : 0,
133                    right: space.group,
134                    bottom: 2,
135                },
136                width: 11,
137                height: 11,
138                color: foreground(theme.lowest)
139            }
140        }
141    }
142    return {
143        userMenuButtonOnline: build_button({ online: true }),
144        userMenuButtonOffline: build_button({ online: false }),
145    }
146}
147
148export function titlebar(theme: ColorScheme) {
149    const avatarWidth = 18
150    const avatarOuterWidth = avatarWidth + 4
151    const followerAvatarWidth = 14
152    const followerAvatarOuterWidth = followerAvatarWidth + 4
153
154    return {
155        item_spacing: ITEM_SPACING,
156        facePileSpacing: 2,
157        height: TITLEBAR_HEIGHT,
158        background: background(theme.lowest),
159        border: border(theme.lowest, { bottom: true }),
160        padding: {
161            left: 80,
162            right: 0,
163        },
164
165        // Project
166        title: text(theme.lowest, "sans", "variant"),
167        highlight_color: text(theme.lowest, "sans", "active").color,
168
169        // Collaborators
170        leaderAvatar: {
171            width: avatarWidth,
172            outerWidth: avatarOuterWidth,
173            cornerRadius: avatarWidth / 2,
174            outerCornerRadius: avatarOuterWidth / 2,
175        },
176        followerAvatar: {
177            width: followerAvatarWidth,
178            outerWidth: followerAvatarOuterWidth,
179            cornerRadius: followerAvatarWidth / 2,
180            outerCornerRadius: followerAvatarOuterWidth / 2,
181        },
182        inactiveAvatarGrayscale: true,
183        followerAvatarOverlap: 8,
184        leaderSelection: {
185            margin: {
186                top: 4,
187                bottom: 4,
188            },
189            padding: {
190                left: 2,
191                right: 2,
192                top: 2,
193                bottom: 2,
194            },
195            cornerRadius: 6,
196        },
197        avatarRibbon: {
198            height: 3,
199            width: 11,
200            // TODO: Chore: Make avatarRibbon colors driven by the theme rather than being hard coded.
201        },
202
203        // Sign in buttom
204        sign_in_button: toggleable_text_button(theme, {}),
205
206        // Offline Indicator
207        offlineIcon: {
208            color: foreground(theme.lowest, "variant"),
209            width: 16,
210            margin: {
211                left: ITEM_SPACING,
212            },
213            padding: {
214                right: 4,
215            },
216        },
217
218        // Notice that the collaboration server is out of date
219        outdatedWarning: {
220            ...text(theme.lowest, "sans", "warning", { size: "xs" }),
221            background: withOpacity(background(theme.lowest, "warning"), 0.3),
222            border: border(theme.lowest, "warning"),
223            margin: {
224                left: ITEM_SPACING,
225            },
226            padding: {
227                left: 8,
228                right: 8,
229            },
230            cornerRadius: 6,
231        },
232
233        leave_call_button: icon_button(theme, {
234            margin: {
235                left: ITEM_SPACING / 2,
236                right: ITEM_SPACING
237            },
238        }),
239
240        ...call_controls(theme),
241
242        toggle_contacts_button: toggleable_icon_button(theme, {
243            margin: {
244                left: ITEM_SPACING,
245                right: ITEM_SPACING / 2
246            },
247        }),
248
249        // Jewel that notifies you that there are new contact requests
250        toggleContactsBadge: {
251            cornerRadius: 3,
252            padding: 2,
253            margin: { top: 3, left: 3 },
254            border: border(theme.lowest),
255            background: foreground(theme.lowest, "accent"),
256        },
257        shareButton: toggleable_text_button(theme, {}),
258        user_menu: user_menu(theme)
259    }
260}