Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement custom titlebars that can be configured in Python #42

Merged
merged 28 commits into from
Jun 16, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
57642c5
Introduce base titlebar component without functionality
jonahbedouch May 30, 2023
06c8764
Implement button, image, and padding insertion without backend
jonahbedouch May 31, 2023
037dfd5
Implement Python Interface for toggling Navbar and adding Buttons, Im…
jonahbedouch Jun 1, 2023
e18650c
Introduce base titlebar component without functionality
jonahbedouch May 30, 2023
fed046c
Implement button, image, and padding insertion without backend
jonahbedouch May 31, 2023
2431ec0
Implement Python Interface for toggling Navbar and adding Buttons, Im…
jonahbedouch Jun 1, 2023
a570a22
Merge branch 'jonah/titlebar' of github.com:nerfstudio-project/viser …
jonahbedouch Jun 1, 2023
8143dd2
Resolve Build Warnings in Python
jonahbedouch Jun 1, 2023
1636bd7
Resolve Formatting Issues
jonahbedouch Jun 1, 2023
32c2930
Introduce base titlebar component without functionality
jonahbedouch May 30, 2023
1e656e2
Implement button, image, and padding insertion without backend
jonahbedouch May 31, 2023
14d2330
Implement Python Interface for toggling Navbar and adding Buttons, Im…
jonahbedouch Jun 1, 2023
99ae570
Introduce base titlebar component without functionality
jonahbedouch May 30, 2023
70c2bd5
Implement button, image, and padding insertion without backend
jonahbedouch May 31, 2023
8135a4b
Implement Python Interface for toggling Navbar and adding Buttons, Im…
jonahbedouch Jun 1, 2023
b59db27
Resolve Build Warnings in Python
jonahbedouch Jun 1, 2023
3d0adb0
Resolve Formatting Issues
jonahbedouch Jun 1, 2023
27cd1ca
Add Support for TypedDicts to the Typescript Type Syncer
jonahbedouch Jun 13, 2023
17ff7de
Update Titlebar Interface to rely on a fixed layout consisting of but…
jonahbedouch Jun 13, 2023
6f9db55
Merge branch 'jonah/titlebar' of github.com:nerfstudio-project/viser …
jonahbedouch Jun 13, 2023
a1a3e06
Merge changes from main branch and build client
jonahbedouch Jun 16, 2023
83a84df
Add docstrings to titlebar config objects
jonahbedouch Jun 16, 2023
8bad1c7
Remove old Titlebar interface from _message_api.py
jonahbedouch Jun 16, 2023
a15a458
Delete viser/.vscode directory
jonahbedouch Jun 16, 2023
82894c7
Merge branch 'main' of github.com:brentyi/viser into jonah/titlebar
brentyi Jun 16, 2023
9c1cc55
Move theming to separate example
brentyi Jun 16, 2023
d65f219
Tweak types
brentyi Jun 16, 2023
84ea450
Run ruff
brentyi Jun 16, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 55 additions & 0 deletions examples/13_theming.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
"""Theming
Viser is adding support for theming. Work-in-progress.
"""

import time


import viser
from viser.theme import TitlebarButton, TitlebarConfig, TitlebarImage

server = viser.ViserServer()

buttons = (
TitlebarButton(
text="Getting Started",
icon=None,
href="https://nerf.studio",
variant="outlined",
),
TitlebarButton(
text="Github",
icon="GitHub",
href="https://github.com/nerfstudio-project/nerfstudio",
variant="outlined",
),
TitlebarButton(
text="Documentation",
icon="Description",
href="https://docs.nerf.studio",
variant="outlined",
),
TitlebarButton(
text="Viewport Controls",
icon="Keyboard",
href="https://docs.nerf.studio",
variant="outlined",
),
)
image = TitlebarImage(
image_url="https://docs.nerf.studio/en/latest/_static/imgs/logo.png",
image_alt="NerfStudio Logo",
href="https://docs.nerf.studio/",
)

titlebar_theme = TitlebarConfig(buttons=buttons, image=image)

server.configure_theme(
canvas_background_color=(2, 230, 230),
titlebar_content=titlebar_theme,
)
server.world_axes.visible = True

while True:
time.sleep(10.0)
Empty file modified examples/assets/download_colmap_garden.sh
100644 → 100755
Empty file.
6 changes: 4 additions & 2 deletions viser/_message_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@
import trimesh.visual
from typing_extensions import Literal, LiteralString, ParamSpec, TypeAlias, assert_never

from . import _messages, infra
from . import _messages, infra, theme
from ._gui import (
GuiButtonGroupHandle,
GuiButtonHandle,
Expand Down Expand Up @@ -164,11 +164,13 @@ def configure_theme(
self,
*,
canvas_background_color: RgbTupleOrArray = (255, 255, 255),
titlebar_content: Optional[theme.TitlebarConfig] = None,
) -> None:
"""Configure the viser front-end's visual appearance."""
self._queue(
_messages.ThemeConfigurationMessage(
canvas_background_color=_encode_rgb(canvas_background_color)
canvas_background_color=_encode_rgb(canvas_background_color),
titlebar_content=titlebar_content,
),
)

Expand Down
2 changes: 2 additions & 0 deletions viser/_messages.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
from typing_extensions import Literal, override

from . import infra
from . import theme


class Message(infra.Message):
Expand Down Expand Up @@ -326,3 +327,4 @@ class ThemeConfigurationMessage(Message):
"""Message from server->client to configure parts of the GUI."""

canvas_background_color: int
titlebar_content: Optional[theme.TitlebarConfig]
6 changes: 3 additions & 3 deletions viser/client/build/asset-manifest.json
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
{
"files": {
"main.css": "/static/css/main.82973013.css",
"main.js": "/static/js/main.78d359b6.js",
"main.js": "/static/js/main.54bd4a4f.js",
"index.html": "/index.html",
"main.82973013.css.map": "/static/css/main.82973013.css.map",
"main.78d359b6.js.map": "/static/js/main.78d359b6.js.map"
"main.54bd4a4f.js.map": "/static/js/main.54bd4a4f.js.map"
},
"entrypoints": [
"static/css/main.82973013.css",
"static/js/main.78d359b6.js"
"static/js/main.54bd4a4f.js"
]
}
2 changes: 1 addition & 1 deletion viser/client/build/index.html
Original file line number Diff line number Diff line change
@@ -1 +1 @@
<!doctype html><html lang="en"><head><meta charset="utf-8"/><link rel="icon" href="/favicon.svg"/><meta name="viewport" content="width=device-width,initial-scale=1"/><meta name="theme-color" content="#000000"/><meta name="description" content="Web site created using create-react-app"/><link rel="apple-touch-icon" href="/logo192.png"/><link rel="manifest" href="/manifest.json"/><title>Viser</title><script defer="defer" src="/static/js/main.78d359b6.js"></script><link href="/static/css/main.82973013.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div></body></html>
<!doctype html><html lang="en"><head><meta charset="utf-8"/><link rel="icon" href="/favicon.svg"/><meta name="viewport" content="width=device-width,initial-scale=1"/><meta name="theme-color" content="#000000"/><meta name="description" content="Web site created using create-react-app"/><link rel="apple-touch-icon" href="/logo192.png"/><link rel="manifest" href="/manifest.json"/><title>Viser</title><script defer="defer" src="/static/js/main.54bd4a4f.js"></script><link href="/static/css/main.82973013.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div></body></html>
3 changes: 3 additions & 0 deletions viser/client/build/static/js/main.54bd4a4f.js

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions viser/client/build/static/js/main.54bd4a4f.js.map

Large diffs are not rendered by default.

3 changes: 0 additions & 3 deletions viser/client/build/static/js/main.78d359b6.js

This file was deleted.

1 change: 0 additions & 1 deletion viser/client/build/static/js/main.78d359b6.js.map

This file was deleted.

1 change: 1 addition & 0 deletions viser/client/src/ControlPanel/GuiState.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ const cleanGuiState: GuiState = {
theme: {
type: "ThemeConfigurationMessage",
canvas_background_color: 0xffffff,
titlebar_content: null,
},
label: "",
server: "ws://localhost:8080", // Currently this will always be overridden.
Expand Down
124 changes: 124 additions & 0 deletions viser/client/src/Titlebar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
import { Button, Grid, IconButton, SvgIcon } from "@mui/material";
import { useContext } from "react";
import { ViewerContext } from ".";
import { Message } from "./WebsocketMessages";

import * as Icons from "@mui/icons-material";

// Type helpers.
type IconName = keyof typeof Icons;
type ArrayElement<ArrayType extends readonly unknown[]> =
ArrayType extends readonly (infer ElementType)[] ? ElementType : never;
type NoNull<T> = Exclude<T, null>;
type TitlebarContent = NoNull<
(Message & { type: "ThemeConfigurationMessage" })["titlebar_content"]
>;

// We inherit props directly from message contents.
export function TitlebarButton(
props: ArrayElement<NoNull<TitlebarContent["buttons"]>>
) {
if (props.icon !== null && props.text === null) {
return (
<IconButton href={props.href ?? ""}>
<SvgIcon component={Icons[props.icon as IconName] ?? null} />
</IconButton>
);
}
return (
<Button
variant={props.variant ?? "contained"}
href={props.href ?? ""}
sx={{
marginY: "0.4em",
marginX: "0.125em",
alignItems: "center",
}}
size="small"
target="_blank"
startIcon={
props.icon ? (
<SvgIcon component={Icons[props.icon as IconName] ?? null} />
) : null
}
>
{props.text ?? ""}
</Button>
);
}

export function TitlebarImage(props: NoNull<TitlebarContent["image"]>) {
const image = (
<img
src={props.image_url}
alt={props.image_alt}
style={{
height: "2em",
marginLeft: "0.125em",
marginRight: "0.125em",
}}
/>
);
if (props.href == null) {
return image;
}
return <a href={props.href}>{image}</a>;
}

export function Titlebar() {
const viewer = useContext(ViewerContext)!;
const content = viewer.useGui((state) => state.theme.titlebar_content);

if (content == null) {
return null;
}

const buttons = content.buttons;
const imageData = content.image;

return (
<Grid
container
sx={{
width: "100%",
zIndex: "1000",
backgroundColor: "rgba(255, 255, 255, 0.85)",
borderBottom: "1px solid",
borderBottomColor: "divider",
direction: "row",
justifyContent: "space-between",
alignItems: "center",
paddingX: "0.875em",
height: "2.5em",
}}
>
<Grid
item
xs="auto"
component="div"
sx={{
display: "flex",
direction: "row",
alignItems: "center",
justifyContent: "left",
overflow: "visible",
}}
>
{buttons?.map((btn) => TitlebarButton(btn))}
</Grid>
<Grid
item
xs={3}
component="div"
sx={{
display: "flex",
direction: "row",
alignItems: "center",
justifyContent: "right",
}}
>
{imageData !== null ? TitlebarImage(imageData) : null}
</Grid>
</Grid>
);
}
11 changes: 11 additions & 0 deletions viser/client/src/WebsocketMessages.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,17 @@ interface MessageGroupEnd {
interface ThemeConfigurationMessage {
type: "ThemeConfigurationMessage";
canvas_background_color: number;
titlebar_content: {
buttons:
| {
text: string | null;
icon: "GitHub" | "Description" | "Keyboard" | null;
href: string | null;
variant: "text" | "contained" | "outlined" | null;
}[]
| null;
image: { image_url: string; image_alt: string; href: string | null } | null;
} | null;
}

export type Message =
Expand Down
8 changes: 7 additions & 1 deletion viser/client/src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ import WebsocketInterface from "./WebsocketInterface";
import { UseGui, useGuiState } from "./ControlPanel/GuiState";
import { searchParamKey } from "./SearchParamsUtils";

import { Titlebar } from "./Titlebar";

type ViewerContextContents = {
useSceneTree: UseSceneTree;
useGui: UseGui;
Expand All @@ -47,8 +49,9 @@ function SingleViewer() {
// Layout and styles.
const Wrapper = styled(Box)`
width: 100%;
height: 100%;
height: 1px;
position: relative;
flex: 1 0 auto;
`;

// Default server logic.
Expand Down Expand Up @@ -80,6 +83,7 @@ function SingleViewer() {
};
return (
<ViewerContext.Provider value={viewer}>
<Titlebar></Titlebar>
<Wrapper ref={viewer.wrapperRef}>
<WebsocketInterface />
<ControlPanel />
Expand Down Expand Up @@ -157,6 +161,8 @@ function Root() {
height: "100%",
boxSizing: "border-box",
position: "relative",
display: "flex",
flexDirection: "column",
}}
>
<SingleViewer />
Expand Down
25 changes: 20 additions & 5 deletions viser/infra/_typescript_interface_gen.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
from typing import Any, ClassVar, Type, Union, get_type_hints

import numpy as onp
from typing_extensions import Literal, get_args, get_origin
from typing_extensions import Literal, get_args, get_origin, is_typeddict

from ._messages import Message

Expand Down Expand Up @@ -32,12 +32,27 @@ def _get_ts_type(typ: Type) -> str:
get_args(typ),
)
)
if is_typeddict(typ):
hints = get_type_hints(typ)

def fmt(key):
val = hints[key]
ret = f"'{key}'" + ": " + _get_ts_type(val)
return ret

ret = "{" + ", ".join(map(fmt, hints)) + "}"
# ret = "{" + f"type: \'{typ.__name__}\', " + ", ".join(map(fmt, hints)) + "}"
return ret
if get_origin(typ) is Union:
return " | ".join(
map(
_get_ts_type,
get_args(typ),
return (
"("
+ " | ".join(
map(
_get_ts_type,
get_args(typ),
)
)
+ ")"
)

if hasattr(typ, "__origin__"):
Expand Down
7 changes: 7 additions & 0 deletions viser/theme/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
""":mod:`viser.theme` provides interfaces for themeing the viser
frontend from within Python.
"""

from ._titlebar import TitlebarButton as TitlebarButton
from ._titlebar import TitlebarConfig as TitlebarConfig
from ._titlebar import TitlebarImage as TitlebarImage
25 changes: 25 additions & 0 deletions viser/theme/_titlebar.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
from typing import Tuple, Literal, Optional, TypedDict


class TitlebarButton(TypedDict):
"""A link-only button that appears in the Titlebar."""

text: Optional[str]
icon: Optional[Literal["GitHub", "Description", "Keyboard"]]
href: Optional[str]
variant: Optional[Literal["text", "contained", "outlined"]]


class TitlebarImage(TypedDict):
"""An image that appears on the titlebar."""

image_url: str
image_alt: str
href: Optional[str]


class TitlebarConfig(TypedDict):
"""Configure the content that appears in the titlebar."""

buttons: Optional[Tuple[TitlebarButton, ...]]
image: Optional[TitlebarImage]