Compare commits
15 commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
2c56602657 | ||
![]() |
d9c1558411 | ||
![]() |
8900c18f98 | ||
![]() |
a5fb428839 | ||
![]() |
a21287d9f8 | ||
![]() |
4f9ec8148b | ||
![]() |
55e9d39596 | ||
![]() |
2ee3ed1fdc | ||
![]() |
d15663393a | ||
![]() |
4f116da026 | ||
![]() |
88bf8872c1 | ||
![]() |
644d32db2a | ||
![]() |
a26744a96c | ||
![]() |
02a426b657 | ||
![]() |
c1bb4e7808 |
108 changed files with 6443 additions and 1948 deletions
17
Cargo.lock
generated
17
Cargo.lock
generated
|
@ -109,6 +109,8 @@ dependencies = [
|
|||
"isahc",
|
||||
"language",
|
||||
"menu",
|
||||
"project",
|
||||
"regex",
|
||||
"schemars",
|
||||
"search",
|
||||
"serde",
|
||||
|
@ -6916,18 +6918,6 @@ dependencies = [
|
|||
"workspace",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "theme_testbench"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"gpui",
|
||||
"project",
|
||||
"settings",
|
||||
"smallvec",
|
||||
"theme",
|
||||
"workspace",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "1.0.40"
|
||||
|
@ -8809,7 +8799,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "zed"
|
||||
version = "0.92.0"
|
||||
version = "0.92.4"
|
||||
dependencies = [
|
||||
"activity_indicator",
|
||||
"ai",
|
||||
|
@ -8888,7 +8878,6 @@ dependencies = [
|
|||
"text",
|
||||
"theme",
|
||||
"theme_selector",
|
||||
"theme_testbench",
|
||||
"thiserror",
|
||||
"tiny_http",
|
||||
"toml",
|
||||
|
|
|
@ -61,7 +61,6 @@ members = [
|
|||
"crates/text",
|
||||
"crates/theme",
|
||||
"crates/theme_selector",
|
||||
"crates/theme_testbench",
|
||||
"crates/util",
|
||||
"crates/vim",
|
||||
"crates/workspace",
|
||||
|
|
1
assets/icons/assist_15.svg
Normal file
1
assets/icons/assist_15.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M13.9 0.499976C13.9 0.279062 13.7209 0.0999756 13.5 0.0999756C13.2791 0.0999756 13.1 0.279062 13.1 0.499976V1.09998H12.5C12.2791 1.09998 12.1 1.27906 12.1 1.49998C12.1 1.72089 12.2791 1.89998 12.5 1.89998H13.1V2.49998C13.1 2.72089 13.2791 2.89998 13.5 2.89998C13.7209 2.89998 13.9 2.72089 13.9 2.49998V1.89998H14.5C14.7209 1.89998 14.9 1.72089 14.9 1.49998C14.9 1.27906 14.7209 1.09998 14.5 1.09998H13.9V0.499976ZM11.8536 3.14642C12.0488 3.34168 12.0488 3.65826 11.8536 3.85353L10.8536 4.85353C10.6583 5.04879 10.3417 5.04879 10.1465 4.85353C9.9512 4.65827 9.9512 4.34169 10.1465 4.14642L11.1464 3.14643C11.3417 2.95116 11.6583 2.95116 11.8536 3.14642ZM9.85357 5.14642C10.0488 5.34168 10.0488 5.65827 9.85357 5.85353L2.85355 12.8535C2.65829 13.0488 2.34171 13.0488 2.14645 12.8535C1.95118 12.6583 1.95118 12.3417 2.14645 12.1464L9.14646 5.14642C9.34172 4.95116 9.65831 4.95116 9.85357 5.14642ZM13.5 5.09998C13.7209 5.09998 13.9 5.27906 13.9 5.49998V6.09998H14.5C14.7209 6.09998 14.9 6.27906 14.9 6.49998C14.9 6.72089 14.7209 6.89998 14.5 6.89998H13.9V7.49998C13.9 7.72089 13.7209 7.89998 13.5 7.89998C13.2791 7.89998 13.1 7.72089 13.1 7.49998V6.89998H12.5C12.2791 6.89998 12.1 6.72089 12.1 6.49998C12.1 6.27906 12.2791 6.09998 12.5 6.09998H13.1V5.49998C13.1 5.27906 13.2791 5.09998 13.5 5.09998ZM8.90002 0.499976C8.90002 0.279062 8.72093 0.0999756 8.50002 0.0999756C8.2791 0.0999756 8.10002 0.279062 8.10002 0.499976V1.09998H7.50002C7.2791 1.09998 7.10002 1.27906 7.10002 1.49998C7.10002 1.72089 7.2791 1.89998 7.50002 1.89998H8.10002V2.49998C8.10002 2.72089 8.2791 2.89998 8.50002 2.89998C8.72093 2.89998 8.90002 2.72089 8.90002 2.49998V1.89998H9.50002C9.72093 1.89998 9.90002 1.72089 9.90002 1.49998C9.90002 1.27906 9.72093 1.09998 9.50002 1.09998H8.90002V0.499976Z" fill="currentColor" fill-rule="evenodd" clip-rule="evenodd"></path></svg>
|
After Width: | Height: | Size: 1.9 KiB |
3
assets/icons/hamburger_15.svg
Normal file
3
assets/icons/hamburger_15.svg
Normal file
|
@ -0,0 +1,3 @@
|
|||
<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M1.5 3C1.22386 3 1 3.22386 1 3.5C1 3.77614 1.22386 4 1.5 4H13.5C13.7761 4 14 3.77614 14 3.5C14 3.22386 13.7761 3 13.5 3H1.5ZM1 7.5C1 7.22386 1.22386 7 1.5 7H13.5C13.7761 7 14 7.22386 14 7.5C14 7.77614 13.7761 8 13.5 8H1.5C1.22386 8 1 7.77614 1 7.5ZM1 11.5C1 11.2239 1.22386 11 1.5 11H13.5C13.7761 11 14 11.2239 14 11.5C14 11.7761 13.7761 12 13.5 12H1.5C1.22386 12 1 11.7761 1 11.5Z" fill="#CCCAC2"/>
|
||||
</svg>
|
After Width: | Height: | Size: 552 B |
1
assets/icons/quote_15.svg
Normal file
1
assets/icons/quote_15.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M9.42503 3.44136C10.0561 3.23654 10.7837 3.2402 11.3792 3.54623C12.7532 4.25224 13.3477 6.07191 12.7946 8C12.5465 8.8649 12.1102 9.70472 11.1861 10.5524C10.262 11.4 8.98034 11.9 8.38571 11.9C8.17269 11.9 8 11.7321 8 11.525C8 11.3179 8.17644 11.15 8.38571 11.15C9.06497 11.15 9.67189 10.7804 10.3906 10.236C10.9406 9.8193 11.3701 9.28633 11.608 8.82191C12.0628 7.93367 12.0782 6.68174 11.3433 6.34901C10.9904 6.73455 10.5295 6.95946 9.97725 6.95946C8.7773 6.95946 8.0701 5.99412 8.10051 5.12009C8.12957 4.28474 8.66032 3.68954 9.42503 3.44136ZM3.42503 3.44136C4.05614 3.23654 4.78366 3.2402 5.37923 3.54623C6.7532 4.25224 7.34766 6.07191 6.79462 8C6.54654 8.8649 6.11019 9.70472 5.1861 10.5524C4.26201 11.4 2.98034 11.9 2.38571 11.9C2.17269 11.9 2 11.7321 2 11.525C2 11.3179 2.17644 11.15 2.38571 11.15C3.06497 11.15 3.67189 10.7804 4.39058 10.236C4.94065 9.8193 5.37014 9.28633 5.60797 8.82191C6.06282 7.93367 6.07821 6.68174 5.3433 6.34901C4.99037 6.73455 4.52948 6.95946 3.97725 6.95946C2.7773 6.95946 2.0701 5.99412 2.10051 5.12009C2.12957 4.28474 2.66032 3.68954 3.42503 3.44136Z" fill="currentColor" fill-rule="evenodd" clip-rule="evenodd"></path></svg>
|
After Width: | Height: | Size: 1.2 KiB |
1
assets/icons/split_message_15.svg
Normal file
1
assets/icons/split_message_15.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M7.81832 0.68179C7.64258 0.506054 7.35766 0.506054 7.18192 0.68179L5.18192 2.68179C5.00619 2.85753 5.00619 3.14245 5.18192 3.31819C5.35766 3.49392 5.64258 3.49392 5.81832 3.31819L7.05012 2.08638L7.05012 5.50023C7.05012 5.74876 7.25159 5.95023 7.50012 5.95023C7.74865 5.95023 7.95012 5.74876 7.95012 5.50023L7.95012 2.08638L9.18192 3.31819C9.35766 3.49392 9.64258 3.49392 9.81832 3.31819C9.99406 3.14245 9.99406 2.85753 9.81832 2.68179L7.81832 0.68179ZM7.95012 12.9136V9.50023C7.95012 9.2517 7.74865 9.05023 7.50012 9.05023C7.25159 9.05023 7.05012 9.2517 7.05012 9.50023V12.9136L5.81832 11.6818C5.64258 11.5061 5.35766 11.5061 5.18192 11.6818C5.00619 11.8575 5.00619 12.1424 5.18192 12.3182L7.18192 14.3182C7.26632 14.4026 7.38077 14.45 7.50012 14.45C7.61947 14.45 7.73393 14.4026 7.81832 14.3182L9.81832 12.3182C9.99406 12.1424 9.99406 11.8575 9.81832 11.6818C9.64258 11.5061 9.35766 11.5061 9.18192 11.6818L7.95012 12.9136ZM1.49994 7.00017C1.2238 7.00017 0.999939 7.22403 0.999939 7.50017C0.999939 7.77631 1.2238 8.00017 1.49994 8.00017L13.4999 8.00017C13.7761 8.00017 13.9999 7.77631 13.9999 7.50017C13.9999 7.22403 13.7761 7.00017 13.4999 7.00017L1.49994 7.00017Z" fill="currentColor" fill-rule="evenodd" clip-rule="evenodd"></path></svg>
|
After Width: | Height: | Size: 1.3 KiB |
|
@ -40,7 +40,8 @@
|
|||
"cmd-o": "workspace::Open",
|
||||
"alt-cmd-o": "projects::OpenRecent",
|
||||
"ctrl-~": "workspace::NewTerminal",
|
||||
"ctrl-`": "terminal_panel::ToggleFocus"
|
||||
"ctrl-`": "terminal_panel::ToggleFocus",
|
||||
"shift-escape": "workspace::ToggleZoom"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
@ -197,9 +198,17 @@
|
|||
}
|
||||
},
|
||||
{
|
||||
"context": "AssistantEditor > Editor",
|
||||
"context": "AssistantPanel",
|
||||
"bindings": {
|
||||
"cmd-g": "search::SelectNextMatch",
|
||||
"cmd-shift-g": "search::SelectPrevMatch"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "ConversationEditor > Editor",
|
||||
"bindings": {
|
||||
"cmd-enter": "assistant::Assist",
|
||||
"cmd-s": "workspace::Save",
|
||||
"cmd->": "assistant::QuoteSelection",
|
||||
"shift-enter": "assistant::Split",
|
||||
"ctrl-r": "assistant::CycleMessageRole"
|
||||
|
@ -234,8 +243,7 @@
|
|||
"cmd-shift-g": "search::SelectPrevMatch",
|
||||
"alt-cmd-c": "search::ToggleCaseSensitive",
|
||||
"alt-cmd-w": "search::ToggleWholeWord",
|
||||
"alt-cmd-r": "search::ToggleRegex",
|
||||
"shift-escape": "workspace::ToggleZoom"
|
||||
"alt-cmd-r": "search::ToggleRegex"
|
||||
}
|
||||
},
|
||||
// Bindings from VS Code
|
||||
|
@ -400,6 +408,7 @@
|
|||
"cmd-shift-p": "command_palette::Toggle",
|
||||
"cmd-shift-m": "diagnostics::Deploy",
|
||||
"cmd-shift-e": "project_panel::ToggleFocus",
|
||||
"cmd-?": "assistant::ToggleFocus",
|
||||
"cmd-alt-s": "workspace::SaveAll",
|
||||
"cmd-k m": "language_selector::Toggle"
|
||||
}
|
||||
|
|
|
@ -57,37 +57,37 @@
|
|||
"show_whitespaces": "selection",
|
||||
// Scrollbar related settings
|
||||
"scrollbar": {
|
||||
// When to show the scrollbar in the editor.
|
||||
// This setting can take four values:
|
||||
//
|
||||
// 1. Show the scrollbar if there's important information or
|
||||
// follow the system's configured behavior (default):
|
||||
// "auto"
|
||||
// 2. Match the system's configured behavior:
|
||||
// "system"
|
||||
// 3. Always show the scrollbar:
|
||||
// "always"
|
||||
// 4. Never show the scrollbar:
|
||||
// "never"
|
||||
"show": "auto",
|
||||
// Whether to show git diff indicators in the scrollbar.
|
||||
"git_diff": true
|
||||
// When to show the scrollbar in the editor.
|
||||
// This setting can take four values:
|
||||
//
|
||||
// 1. Show the scrollbar if there's important information or
|
||||
// follow the system's configured behavior (default):
|
||||
// "auto"
|
||||
// 2. Match the system's configured behavior:
|
||||
// "system"
|
||||
// 3. Always show the scrollbar:
|
||||
// "always"
|
||||
// 4. Never show the scrollbar:
|
||||
// "never"
|
||||
"show": "auto",
|
||||
// Whether to show git diff indicators in the scrollbar.
|
||||
"git_diff": true
|
||||
},
|
||||
"project_panel": {
|
||||
// Whether to show the git status in the project panel.
|
||||
"git_status": true,
|
||||
// Where to dock project panel. Can be 'left' or 'right'.
|
||||
"dock": "left",
|
||||
// Default width of the project panel.
|
||||
"default_width": 240
|
||||
// Whether to show the git status in the project panel.
|
||||
"git_status": true,
|
||||
// Where to dock project panel. Can be 'left' or 'right'.
|
||||
"dock": "left",
|
||||
// Default width of the project panel.
|
||||
"default_width": 240
|
||||
},
|
||||
"assistant": {
|
||||
// Where to dock the assistant. Can be 'left', 'right' or 'bottom'.
|
||||
"dock": "right",
|
||||
// Default width when the assistant is docked to the left or right.
|
||||
"default_width": 450,
|
||||
// Default height when the assistant is docked to the bottom.
|
||||
"default_height": 320
|
||||
// Where to dock the assistant. Can be 'left', 'right' or 'bottom'.
|
||||
"dock": "right",
|
||||
// Default width when the assistant is docked to the left or right.
|
||||
"default_width": 640,
|
||||
// Default height when the assistant is docked to the bottom.
|
||||
"default_height": 320
|
||||
},
|
||||
// Whether the screen sharing icon is shown in the os status bar.
|
||||
"show_call_status_icon": true,
|
||||
|
|
|
@ -326,7 +326,7 @@ impl View for ActivityIndicator {
|
|||
let mut element = MouseEventHandler::<Self, _>::new(0, cx, |state, cx| {
|
||||
let theme = &theme::current(cx).workspace.status_bar.lsp_status;
|
||||
let style = if state.hovered() && on_click.is_some() {
|
||||
theme.hover.as_ref().unwrap_or(&theme.default)
|
||||
theme.hovered.as_ref().unwrap_or(&theme.default)
|
||||
} else {
|
||||
&theme.default
|
||||
};
|
||||
|
|
|
@ -22,9 +22,10 @@ util = { path = "../util" }
|
|||
workspace = { path = "../workspace" }
|
||||
|
||||
anyhow.workspace = true
|
||||
chrono = "0.4"
|
||||
chrono = { version = "0.4", features = ["serde"] }
|
||||
futures.workspace = true
|
||||
isahc.workspace = true
|
||||
regex.workspace = true
|
||||
schemars.workspace = true
|
||||
serde.workspace = true
|
||||
serde_json.workspace = true
|
||||
|
@ -33,3 +34,4 @@ tiktoken-rs = "0.4"
|
|||
|
||||
[dev-dependencies]
|
||||
editor = { path = "../editor", features = ["test-support"] }
|
||||
project = { path = "../project", features = ["test-support"] }
|
||||
|
|
|
@ -1,10 +1,22 @@
|
|||
pub mod assistant;
|
||||
mod assistant_settings;
|
||||
|
||||
use anyhow::Result;
|
||||
pub use assistant::AssistantPanel;
|
||||
use chrono::{DateTime, Local};
|
||||
use collections::HashMap;
|
||||
use fs::Fs;
|
||||
use futures::StreamExt;
|
||||
use gpui::AppContext;
|
||||
use regex::Regex;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::fmt::{self, Display};
|
||||
use std::{
|
||||
cmp::Reverse,
|
||||
fmt::{self, Display},
|
||||
path::PathBuf,
|
||||
sync::Arc,
|
||||
};
|
||||
use util::paths::CONVERSATIONS_DIR;
|
||||
|
||||
// Data types for chat completion requests
|
||||
#[derive(Debug, Serialize)]
|
||||
|
@ -14,6 +26,84 @@ struct OpenAIRequest {
|
|||
stream: bool,
|
||||
}
|
||||
|
||||
#[derive(
|
||||
Copy, Clone, Debug, Default, Eq, PartialEq, PartialOrd, Ord, Hash, Serialize, Deserialize,
|
||||
)]
|
||||
struct MessageId(usize);
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
struct MessageMetadata {
|
||||
role: Role,
|
||||
sent_at: DateTime<Local>,
|
||||
status: MessageStatus,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
enum MessageStatus {
|
||||
Pending,
|
||||
Done,
|
||||
Error(Arc<str>),
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
struct SavedMessage {
|
||||
id: MessageId,
|
||||
start: usize,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
struct SavedConversation {
|
||||
zed: String,
|
||||
version: String,
|
||||
text: String,
|
||||
messages: Vec<SavedMessage>,
|
||||
message_metadata: HashMap<MessageId, MessageMetadata>,
|
||||
summary: String,
|
||||
model: String,
|
||||
}
|
||||
|
||||
impl SavedConversation {
|
||||
const VERSION: &'static str = "0.1.0";
|
||||
}
|
||||
|
||||
struct SavedConversationMetadata {
|
||||
title: String,
|
||||
path: PathBuf,
|
||||
mtime: chrono::DateTime<chrono::Local>,
|
||||
}
|
||||
|
||||
impl SavedConversationMetadata {
|
||||
pub async fn list(fs: Arc<dyn Fs>) -> Result<Vec<Self>> {
|
||||
fs.create_dir(&CONVERSATIONS_DIR).await?;
|
||||
|
||||
let mut paths = fs.read_dir(&CONVERSATIONS_DIR).await?;
|
||||
let mut conversations = Vec::<SavedConversationMetadata>::new();
|
||||
while let Some(path) = paths.next().await {
|
||||
let path = path?;
|
||||
|
||||
let pattern = r" - \d+.zed.json$";
|
||||
let re = Regex::new(pattern).unwrap();
|
||||
|
||||
let metadata = fs.metadata(&path).await?;
|
||||
if let Some((file_name, metadata)) = path
|
||||
.file_name()
|
||||
.and_then(|name| name.to_str())
|
||||
.zip(metadata)
|
||||
{
|
||||
let title = re.replace(file_name, "");
|
||||
conversations.push(Self {
|
||||
title: title.into_owned(),
|
||||
path,
|
||||
mtime: metadata.mtime.into(),
|
||||
});
|
||||
}
|
||||
}
|
||||
conversations.sort_unstable_by_key(|conversation| Reverse(conversation.mtime));
|
||||
|
||||
Ok(conversations)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Eq, PartialEq)]
|
||||
struct RequestMessage {
|
||||
role: Role,
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -49,7 +49,7 @@ impl View for UpdateNotification {
|
|||
)
|
||||
.with_child(
|
||||
MouseEventHandler::<Cancel, _>::new(0, cx, |state, _| {
|
||||
let style = theme.dismiss_button.style_for(state, false);
|
||||
let style = theme.dismiss_button.style_for(state);
|
||||
Svg::new("icons/x_mark_8.svg")
|
||||
.with_color(style.color)
|
||||
.constrained()
|
||||
|
@ -74,7 +74,7 @@ impl View for UpdateNotification {
|
|||
),
|
||||
)
|
||||
.with_child({
|
||||
let style = theme.action_message.style_for(state, false);
|
||||
let style = theme.action_message.style_for(state);
|
||||
Text::new("View the release notes", style.text.clone())
|
||||
.contained()
|
||||
.with_style(style.container)
|
||||
|
|
|
@ -83,7 +83,7 @@ impl View for Breadcrumbs {
|
|||
}
|
||||
|
||||
MouseEventHandler::<Breadcrumbs, Breadcrumbs>::new(0, cx, |state, _| {
|
||||
let style = style.style_for(state, false);
|
||||
let style = style.style_for(state);
|
||||
crumbs.with_style(style.container)
|
||||
})
|
||||
.on_click(MouseButton::Left, |_, this, cx| {
|
||||
|
|
|
@ -299,7 +299,12 @@ impl CollabTitlebarItem {
|
|||
pub fn toggle_user_menu(&mut self, _: &ToggleUserMenu, cx: &mut ViewContext<Self>) {
|
||||
let theme = theme::current(cx).clone();
|
||||
let avatar_style = theme.workspace.titlebar.leader_avatar.clone();
|
||||
let item_style = theme.context_menu.item.disabled_style().clone();
|
||||
let item_style = theme
|
||||
.context_menu
|
||||
.item
|
||||
.inactive_state()
|
||||
.disabled_style()
|
||||
.clone();
|
||||
self.user_menu.update(cx, |user_menu, cx| {
|
||||
let items = if let Some(user) = self.user_store.read(cx).current_user() {
|
||||
vec![
|
||||
|
@ -361,8 +366,20 @@ impl CollabTitlebarItem {
|
|||
.contained()
|
||||
.with_style(titlebar.toggle_contacts_badge)
|
||||
.contained()
|
||||
.with_margin_left(titlebar.toggle_contacts_button.default.icon_width)
|
||||
.with_margin_top(titlebar.toggle_contacts_button.default.icon_width)
|
||||
.with_margin_left(
|
||||
titlebar
|
||||
.toggle_contacts_button
|
||||
.inactive_state()
|
||||
.default
|
||||
.icon_width,
|
||||
)
|
||||
.with_margin_top(
|
||||
titlebar
|
||||
.toggle_contacts_button
|
||||
.inactive_state()
|
||||
.default
|
||||
.icon_width,
|
||||
)
|
||||
.aligned(),
|
||||
)
|
||||
};
|
||||
|
@ -372,7 +389,8 @@ impl CollabTitlebarItem {
|
|||
MouseEventHandler::<ToggleContactsMenu, Self>::new(0, cx, |state, _| {
|
||||
let style = titlebar
|
||||
.toggle_contacts_button
|
||||
.style_for(state, self.contacts_popover.is_some());
|
||||
.in_state(self.contacts_popover.is_some())
|
||||
.style_for(state);
|
||||
Svg::new("icons/user_plus_16.svg")
|
||||
.with_color(style.color)
|
||||
.constrained()
|
||||
|
@ -419,7 +437,7 @@ impl CollabTitlebarItem {
|
|||
|
||||
let titlebar = &theme.workspace.titlebar;
|
||||
MouseEventHandler::<ToggleScreenSharing, Self>::new(0, cx, |state, _| {
|
||||
let style = titlebar.call_control.style_for(state, false);
|
||||
let style = titlebar.call_control.style_for(state);
|
||||
Svg::new(icon)
|
||||
.with_color(style.color)
|
||||
.constrained()
|
||||
|
@ -473,7 +491,7 @@ impl CollabTitlebarItem {
|
|||
.with_child(
|
||||
MouseEventHandler::<ShareUnshare, Self>::new(0, cx, |state, _| {
|
||||
//TODO: Ensure this button has consistent width for both text variations
|
||||
let style = titlebar.share_button.style_for(state, false);
|
||||
let style = titlebar.share_button.inactive_state().style_for(state);
|
||||
Label::new(label, style.text.clone())
|
||||
.contained()
|
||||
.with_style(style.container)
|
||||
|
@ -511,7 +529,7 @@ impl CollabTitlebarItem {
|
|||
Stack::new()
|
||||
.with_child(
|
||||
MouseEventHandler::<ToggleUserMenu, Self>::new(0, cx, |state, _| {
|
||||
let style = titlebar.call_control.style_for(state, false);
|
||||
let style = titlebar.call_control.style_for(state);
|
||||
Svg::new("icons/ellipsis_14.svg")
|
||||
.with_color(style.color)
|
||||
.constrained()
|
||||
|
@ -549,7 +567,7 @@ impl CollabTitlebarItem {
|
|||
fn render_sign_in_button(&self, theme: &Theme, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
|
||||
let titlebar = &theme.workspace.titlebar;
|
||||
MouseEventHandler::<SignIn, Self>::new(0, cx, |state, _| {
|
||||
let style = titlebar.sign_in_prompt.style_for(state, false);
|
||||
let style = titlebar.sign_in_prompt.inactive_state().style_for(state);
|
||||
Label::new("Sign In", style.text.clone())
|
||||
.contained()
|
||||
.with_style(style.container)
|
||||
|
|
|
@ -117,7 +117,8 @@ impl PickerDelegate for ContactFinderDelegate {
|
|||
.contact_finder
|
||||
.picker
|
||||
.item
|
||||
.style_for(mouse_state, selected);
|
||||
.in_state(selected)
|
||||
.style_for(mouse_state);
|
||||
Flex::row()
|
||||
.with_children(user.avatar.clone().map(|avatar| {
|
||||
Image::from_data(avatar)
|
||||
|
|
|
@ -774,7 +774,8 @@ impl ContactList {
|
|||
.with_style(
|
||||
*theme
|
||||
.contact_row
|
||||
.style_for(&mut Default::default(), is_selected),
|
||||
.in_state(is_selected)
|
||||
.style_for(&mut Default::default()),
|
||||
)
|
||||
.into_any()
|
||||
}
|
||||
|
@ -797,7 +798,7 @@ impl ContactList {
|
|||
.width
|
||||
.or(theme.contact_avatar.height)
|
||||
.unwrap_or(0.);
|
||||
let row = &theme.project_row.default;
|
||||
let row = &theme.project_row.inactive_state().default;
|
||||
let tree_branch = theme.tree_branch;
|
||||
let line_height = row.name.text.line_height(font_cache);
|
||||
let cap_height = row.name.text.cap_height(font_cache);
|
||||
|
@ -810,8 +811,11 @@ impl ContactList {
|
|||
};
|
||||
|
||||
MouseEventHandler::<JoinProject, Self>::new(project_id as usize, cx, |mouse_state, _| {
|
||||
let tree_branch = *tree_branch.style_for(mouse_state, is_selected);
|
||||
let row = theme.project_row.style_for(mouse_state, is_selected);
|
||||
let tree_branch = *tree_branch.in_state(is_selected).style_for(mouse_state);
|
||||
let row = theme
|
||||
.project_row
|
||||
.in_state(is_selected)
|
||||
.style_for(mouse_state);
|
||||
|
||||
Flex::row()
|
||||
.with_child(
|
||||
|
@ -893,7 +897,7 @@ impl ContactList {
|
|||
.width
|
||||
.or(theme.contact_avatar.height)
|
||||
.unwrap_or(0.);
|
||||
let row = &theme.project_row.default;
|
||||
let row = &theme.project_row.inactive_state().default;
|
||||
let tree_branch = theme.tree_branch;
|
||||
let line_height = row.name.text.line_height(font_cache);
|
||||
let cap_height = row.name.text.cap_height(font_cache);
|
||||
|
@ -904,8 +908,11 @@ impl ContactList {
|
|||
peer_id.as_u64() as usize,
|
||||
cx,
|
||||
|mouse_state, _| {
|
||||
let tree_branch = *tree_branch.style_for(mouse_state, is_selected);
|
||||
let row = theme.project_row.style_for(mouse_state, is_selected);
|
||||
let tree_branch = *tree_branch.in_state(is_selected).style_for(mouse_state);
|
||||
let row = theme
|
||||
.project_row
|
||||
.in_state(is_selected)
|
||||
.style_for(mouse_state);
|
||||
|
||||
Flex::row()
|
||||
.with_child(
|
||||
|
@ -989,7 +996,8 @@ impl ContactList {
|
|||
|
||||
let header_style = theme
|
||||
.header_row
|
||||
.style_for(&mut Default::default(), is_selected);
|
||||
.in_state(is_selected)
|
||||
.style_for(&mut Default::default());
|
||||
let text = match section {
|
||||
Section::ActiveCall => "Collaborators",
|
||||
Section::Requests => "Contact Requests",
|
||||
|
@ -999,7 +1007,7 @@ impl ContactList {
|
|||
let leave_call = if section == Section::ActiveCall {
|
||||
Some(
|
||||
MouseEventHandler::<LeaveCallContactList, Self>::new(0, cx, |state, _| {
|
||||
let style = theme.leave_call.style_for(state, false);
|
||||
let style = theme.leave_call.style_for(state);
|
||||
Label::new("Leave Call", style.text.clone())
|
||||
.contained()
|
||||
.with_style(style.container)
|
||||
|
@ -1110,8 +1118,7 @@ impl ContactList {
|
|||
contact.user.id as usize,
|
||||
cx,
|
||||
|mouse_state, _| {
|
||||
let button_style =
|
||||
theme.contact_button.style_for(mouse_state, false);
|
||||
let button_style = theme.contact_button.style_for(mouse_state);
|
||||
render_icon_button(button_style, "icons/x_mark_8.svg")
|
||||
.aligned()
|
||||
.flex_float()
|
||||
|
@ -1146,7 +1153,8 @@ impl ContactList {
|
|||
.with_style(
|
||||
*theme
|
||||
.contact_row
|
||||
.style_for(&mut Default::default(), is_selected),
|
||||
.in_state(is_selected)
|
||||
.style_for(&mut Default::default()),
|
||||
)
|
||||
})
|
||||
.on_click(MouseButton::Left, move |_, this, cx| {
|
||||
|
@ -1204,7 +1212,7 @@ impl ContactList {
|
|||
let button_style = if is_contact_request_pending {
|
||||
&theme.disabled_button
|
||||
} else {
|
||||
theme.contact_button.style_for(mouse_state, false)
|
||||
theme.contact_button.style_for(mouse_state)
|
||||
};
|
||||
render_icon_button(button_style, "icons/x_mark_8.svg").aligned()
|
||||
})
|
||||
|
@ -1227,7 +1235,7 @@ impl ContactList {
|
|||
let button_style = if is_contact_request_pending {
|
||||
&theme.disabled_button
|
||||
} else {
|
||||
theme.contact_button.style_for(mouse_state, false)
|
||||
theme.contact_button.style_for(mouse_state)
|
||||
};
|
||||
render_icon_button(button_style, "icons/check_8.svg")
|
||||
.aligned()
|
||||
|
@ -1250,7 +1258,7 @@ impl ContactList {
|
|||
let button_style = if is_contact_request_pending {
|
||||
&theme.disabled_button
|
||||
} else {
|
||||
theme.contact_button.style_for(mouse_state, false)
|
||||
theme.contact_button.style_for(mouse_state)
|
||||
};
|
||||
render_icon_button(button_style, "icons/x_mark_8.svg")
|
||||
.aligned()
|
||||
|
@ -1277,7 +1285,8 @@ impl ContactList {
|
|||
.with_style(
|
||||
*theme
|
||||
.contact_row
|
||||
.style_for(&mut Default::default(), is_selected),
|
||||
.in_state(is_selected)
|
||||
.style_for(&mut Default::default()),
|
||||
)
|
||||
.into_any()
|
||||
}
|
||||
|
|
|
@ -53,7 +53,7 @@ where
|
|||
)
|
||||
.with_child(
|
||||
MouseEventHandler::<Dismiss, V>::new(user.id as usize, cx, |state, _| {
|
||||
let style = theme.dismiss_button.style_for(state, false);
|
||||
let style = theme.dismiss_button.style_for(state);
|
||||
Svg::new("icons/x_mark_8.svg")
|
||||
.with_color(style.color)
|
||||
.constrained()
|
||||
|
@ -93,7 +93,7 @@ where
|
|||
.with_children(buttons.into_iter().enumerate().map(
|
||||
|(ix, (message, handler))| {
|
||||
MouseEventHandler::<Button, V>::new(ix, cx, |state, _| {
|
||||
let button = theme.button.style_for(state, false);
|
||||
let button = theme.button.style_for(state);
|
||||
Label::new(message, button.text.clone())
|
||||
.contained()
|
||||
.with_style(button.container)
|
||||
|
|
|
@ -185,8 +185,8 @@ impl PickerDelegate for CommandPaletteDelegate {
|
|||
let mat = &self.matches[ix];
|
||||
let command = &self.actions[mat.candidate_id];
|
||||
let theme = theme::current(cx);
|
||||
let style = theme.picker.item.style_for(mouse_state, selected);
|
||||
let key_style = &theme.command_palette.key.style_for(mouse_state, selected);
|
||||
let style = theme.picker.item.in_state(selected).style_for(mouse_state);
|
||||
let key_style = &theme.command_palette.key.in_state(selected);
|
||||
let keystroke_spacing = theme.command_palette.keystroke_spacing;
|
||||
|
||||
Flex::row()
|
||||
|
|
|
@ -328,10 +328,8 @@ impl ContextMenu {
|
|||
Flex::column().with_children(self.items.iter().enumerate().map(|(ix, item)| {
|
||||
match item {
|
||||
ContextMenuItem::Item { label, .. } => {
|
||||
let style = style.item.style_for(
|
||||
&mut Default::default(),
|
||||
Some(ix) == self.selected_index,
|
||||
);
|
||||
let style = style.item.in_state(self.selected_index == Some(ix));
|
||||
let style = style.style_for(&mut Default::default());
|
||||
|
||||
match label {
|
||||
ContextMenuItemLabel::String(label) => {
|
||||
|
@ -363,10 +361,8 @@ impl ContextMenu {
|
|||
.with_children(self.items.iter().enumerate().map(|(ix, item)| {
|
||||
match item {
|
||||
ContextMenuItem::Item { action, .. } => {
|
||||
let style = style.item.style_for(
|
||||
&mut Default::default(),
|
||||
Some(ix) == self.selected_index,
|
||||
);
|
||||
let style = style.item.in_state(self.selected_index == Some(ix));
|
||||
let style = style.style_for(&mut Default::default());
|
||||
|
||||
match action {
|
||||
ContextMenuItemAction::Action(action) => KeystrokeLabel::new(
|
||||
|
@ -412,8 +408,8 @@ impl ContextMenu {
|
|||
let action = action.clone();
|
||||
let view_id = self.parent_view_id;
|
||||
MouseEventHandler::<MenuItem, ContextMenu>::new(ix, cx, |state, _| {
|
||||
let style =
|
||||
style.item.style_for(state, Some(ix) == self.selected_index);
|
||||
let style = style.item.in_state(self.selected_index == Some(ix));
|
||||
let style = style.style_for(state);
|
||||
let keystroke = match &action {
|
||||
ContextMenuItemAction::Action(action) => Some(
|
||||
KeystrokeLabel::new(
|
||||
|
|
|
@ -127,16 +127,16 @@ impl CopilotCodeVerification {
|
|||
.with_child(
|
||||
Label::new(
|
||||
if copied { "Copied!" } else { "Copy" },
|
||||
device_code_style.cta.style_for(state, false).text.clone(),
|
||||
device_code_style.cta.style_for(state).text.clone(),
|
||||
)
|
||||
.aligned()
|
||||
.contained()
|
||||
.with_style(*device_code_style.right_container.style_for(state, false))
|
||||
.with_style(*device_code_style.right_container.style_for(state))
|
||||
.constrained()
|
||||
.with_width(device_code_style.right),
|
||||
)
|
||||
.contained()
|
||||
.with_style(device_code_style.cta.style_for(state, false).container)
|
||||
.with_style(device_code_style.cta.style_for(state).container)
|
||||
})
|
||||
.on_click(gpui::platform::MouseButton::Left, {
|
||||
let user_code = data.user_code.clone();
|
||||
|
|
|
@ -71,7 +71,8 @@ impl View for CopilotButton {
|
|||
.status_bar
|
||||
.panel_buttons
|
||||
.button
|
||||
.style_for(state, active);
|
||||
.in_state(active)
|
||||
.style_for(state);
|
||||
|
||||
Flex::row()
|
||||
.with_child(
|
||||
|
@ -255,7 +256,7 @@ impl CopilotButton {
|
|||
move |state: &mut MouseState, style: &theme::ContextMenuItem| {
|
||||
Flex::row()
|
||||
.with_child(Label::new("Copilot Settings", style.label.clone()))
|
||||
.with_child(theme::ui::icon(icon_style.style_for(state, false)))
|
||||
.with_child(theme::ui::icon(icon_style.style_for(state)))
|
||||
.align_children_center()
|
||||
.into_any()
|
||||
},
|
||||
|
|
|
@ -100,7 +100,7 @@ impl View for DiagnosticIndicator {
|
|||
.workspace
|
||||
.status_bar
|
||||
.diagnostic_summary
|
||||
.style_for(state, false);
|
||||
.style_for(state);
|
||||
|
||||
let mut summary_row = Flex::row();
|
||||
if self.summary.error_count > 0 {
|
||||
|
@ -198,7 +198,7 @@ impl View for DiagnosticIndicator {
|
|||
MouseEventHandler::<Message, _>::new(1, cx, |state, _| {
|
||||
Label::new(
|
||||
diagnostic.message.split('\n').next().unwrap().to_string(),
|
||||
message_style.style_for(state, false).text.clone(),
|
||||
message_style.style_for(state).text.clone(),
|
||||
)
|
||||
.aligned()
|
||||
.contained()
|
||||
|
|
|
@ -3320,15 +3320,21 @@ impl Editor {
|
|||
pub fn render_code_actions_indicator(
|
||||
&self,
|
||||
style: &EditorStyle,
|
||||
active: bool,
|
||||
is_active: bool,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) -> Option<AnyElement<Self>> {
|
||||
if self.available_code_actions.is_some() {
|
||||
enum CodeActions {}
|
||||
Some(
|
||||
MouseEventHandler::<CodeActions, _>::new(0, cx, |state, _| {
|
||||
Svg::new("icons/bolt_8.svg")
|
||||
.with_color(style.code_actions.indicator.style_for(state, active).color)
|
||||
Svg::new("icons/bolt_8.svg").with_color(
|
||||
style
|
||||
.code_actions
|
||||
.indicator
|
||||
.in_state(is_active)
|
||||
.style_for(state)
|
||||
.color,
|
||||
)
|
||||
})
|
||||
.with_cursor_style(CursorStyle::PointingHand)
|
||||
.with_padding(Padding::uniform(3.))
|
||||
|
@ -3378,10 +3384,8 @@ impl Editor {
|
|||
.with_color(
|
||||
style
|
||||
.indicator
|
||||
.style_for(
|
||||
mouse_state,
|
||||
fold_status == FoldStatus::Folded,
|
||||
)
|
||||
.in_state(fold_status == FoldStatus::Folded)
|
||||
.style_for(mouse_state)
|
||||
.color,
|
||||
)
|
||||
.constrained()
|
||||
|
|
|
@ -1529,7 +1529,7 @@ impl EditorElement {
|
|||
|
||||
enum JumpIcon {}
|
||||
MouseEventHandler::<JumpIcon, _>::new((*id).into(), cx, |state, _| {
|
||||
let style = style.jump_icon.style_for(state, false);
|
||||
let style = style.jump_icon.style_for(state);
|
||||
Svg::new("icons/arrow_up_right_8.svg")
|
||||
.with_color(style.color)
|
||||
.constrained()
|
||||
|
@ -2094,7 +2094,7 @@ impl Element<Editor> for EditorElement {
|
|||
.folds
|
||||
.ellipses
|
||||
.background
|
||||
.style_for(&mut cx.mouse_state::<FoldMarkers>(id as usize), false)
|
||||
.style_for(&mut cx.mouse_state::<FoldMarkers>(id as usize))
|
||||
.color;
|
||||
|
||||
(id, fold, color)
|
||||
|
|
|
@ -41,7 +41,8 @@ impl View for DeployFeedbackButton {
|
|||
.status_bar
|
||||
.panel_buttons
|
||||
.button
|
||||
.style_for(state, active);
|
||||
.in_state(active)
|
||||
.style_for(state);
|
||||
|
||||
Svg::new("icons/feedback_16.svg")
|
||||
.with_color(style.icon_color)
|
||||
|
|
|
@ -48,7 +48,7 @@ impl View for SubmitFeedbackButton {
|
|||
let theme = theme::current(cx).clone();
|
||||
enum SubmitFeedbackButton {}
|
||||
MouseEventHandler::<SubmitFeedbackButton, Self>::new(0, cx, |state, _| {
|
||||
let style = theme.feedback.submit_button.style_for(state, false);
|
||||
let style = theme.feedback.submit_button.style_for(state);
|
||||
Label::new("Submit as Markdown", style.text.clone())
|
||||
.contained()
|
||||
.with_style(style.container)
|
||||
|
|
|
@ -546,7 +546,7 @@ impl PickerDelegate for FileFinderDelegate {
|
|||
.get(ix)
|
||||
.expect("Invalid matches state: no element for index {ix}");
|
||||
let theme = theme::current(cx);
|
||||
let style = theme.picker.item.style_for(mouse_state, selected);
|
||||
let style = theme.picker.item.in_state(selected).style_for(mouse_state);
|
||||
let (file_name, file_name_positions, full_path, full_path_positions) =
|
||||
self.labels_for_match(path_match, cx, ix);
|
||||
Flex::column()
|
||||
|
|
|
@ -152,6 +152,29 @@ impl App {
|
|||
asset_source,
|
||||
))));
|
||||
|
||||
foreground_platform.on_event(Box::new({
|
||||
let cx = app.0.clone();
|
||||
move |event| {
|
||||
if let Event::KeyDown(KeyDownEvent { keystroke, .. }) = &event {
|
||||
// Allow system menu "cmd-?" shortcut to be overridden
|
||||
if keystroke.cmd
|
||||
&& !keystroke.shift
|
||||
&& !keystroke.alt
|
||||
&& !keystroke.function
|
||||
&& keystroke.key == "?"
|
||||
{
|
||||
if cx
|
||||
.borrow_mut()
|
||||
.update_active_window(|cx| cx.dispatch_keystroke(keystroke))
|
||||
.unwrap_or(false)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
}));
|
||||
foreground_platform.on_quit(Box::new({
|
||||
let cx = app.0.clone();
|
||||
move || {
|
||||
|
|
|
@ -164,6 +164,7 @@ impl<V: View> Element<V> for Label {
|
|||
_: &mut V,
|
||||
cx: &mut ViewContext<V>,
|
||||
) -> Self::PaintState {
|
||||
let visible_bounds = bounds.intersection(visible_bounds).unwrap_or_default();
|
||||
line.paint(
|
||||
scene,
|
||||
bounds.origin(),
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
use std::{borrow::Cow, ops::Range};
|
||||
|
||||
use serde_json::json;
|
||||
|
||||
use super::constrain_size_preserving_aspect_ratio;
|
||||
use crate::json::ToJson;
|
||||
use crate::{
|
||||
color::Color,
|
||||
geometry::{
|
||||
|
@ -10,6 +8,9 @@ use crate::{
|
|||
},
|
||||
scene, Element, LayoutContext, SceneBuilder, SizeConstraint, View, ViewContext,
|
||||
};
|
||||
use serde_derive::Deserialize;
|
||||
use serde_json::json;
|
||||
use std::{borrow::Cow, ops::Range};
|
||||
|
||||
pub struct Svg {
|
||||
path: Cow<'static, str>,
|
||||
|
@ -24,6 +25,14 @@ impl Svg {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn for_style<V: View>(style: SvgStyle) -> impl Element<V> {
|
||||
Self::new(style.asset)
|
||||
.with_color(style.color)
|
||||
.constrained()
|
||||
.with_width(style.dimensions.width)
|
||||
.with_height(style.dimensions.height)
|
||||
}
|
||||
|
||||
pub fn with_color(mut self, color: Color) -> Self {
|
||||
self.color = color;
|
||||
self
|
||||
|
@ -105,9 +114,24 @@ impl<V: View> Element<V> for Svg {
|
|||
}
|
||||
}
|
||||
|
||||
use crate::json::ToJson;
|
||||
#[derive(Clone, Deserialize, Default)]
|
||||
pub struct SvgStyle {
|
||||
pub color: Color,
|
||||
pub asset: String,
|
||||
pub dimensions: Dimensions,
|
||||
}
|
||||
|
||||
use super::constrain_size_preserving_aspect_ratio;
|
||||
#[derive(Clone, Deserialize, Default)]
|
||||
pub struct Dimensions {
|
||||
pub width: f32,
|
||||
pub height: f32,
|
||||
}
|
||||
|
||||
impl Dimensions {
|
||||
pub fn to_vec(&self) -> Vector2F {
|
||||
vec2f(self.width, self.height)
|
||||
}
|
||||
}
|
||||
|
||||
fn from_usvg_rect(rect: usvg::Rect) -> RectF {
|
||||
RectF::new(
|
||||
|
|
|
@ -939,7 +939,6 @@ extern "C" fn send_event(this: &mut Object, _sel: Sel, native_event: id) {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
msg_send![super(this, class!(NSApplication)), sendEvent: native_event]
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,7 +11,7 @@ use std::{
|
|||
cell::RefCell,
|
||||
cmp::{self, Ordering, Reverse},
|
||||
collections::BinaryHeap,
|
||||
iter,
|
||||
fmt, iter,
|
||||
ops::{Deref, DerefMut, Range},
|
||||
sync::Arc,
|
||||
};
|
||||
|
@ -428,6 +428,8 @@ impl SyntaxSnapshot {
|
|||
invalidated_ranges: Vec<Range<usize>>,
|
||||
registry: Option<&Arc<LanguageRegistry>>,
|
||||
) {
|
||||
log::trace!("reparse. invalidated ranges:{:?}", invalidated_ranges);
|
||||
|
||||
let max_depth = self.layers.summary().max_depth;
|
||||
let mut cursor = self.layers.cursor::<SyntaxLayerSummary>();
|
||||
cursor.next(&text);
|
||||
|
@ -489,6 +491,15 @@ impl SyntaxSnapshot {
|
|||
let Some(layer) = cursor.item() else { break };
|
||||
|
||||
if changed_regions.intersects(&layer, text) {
|
||||
if let SyntaxLayerContent::Parsed { language, .. } = &layer.content {
|
||||
log::trace!(
|
||||
"discard layer. language:{}, range:{:?}. changed_regions:{:?}",
|
||||
language.name(),
|
||||
LogAnchorRange(&layer.range, text),
|
||||
LogChangedRegions(&changed_regions, text),
|
||||
);
|
||||
}
|
||||
|
||||
changed_regions.insert(
|
||||
ChangedRegion {
|
||||
depth: layer.depth + 1,
|
||||
|
@ -541,26 +552,24 @@ impl SyntaxSnapshot {
|
|||
.to_ts_point();
|
||||
}
|
||||
|
||||
if included_ranges.is_empty() {
|
||||
included_ranges.push(tree_sitter::Range {
|
||||
start_byte: 0,
|
||||
end_byte: 0,
|
||||
start_point: Default::default(),
|
||||
end_point: Default::default(),
|
||||
});
|
||||
}
|
||||
|
||||
if let Some(SyntaxLayerContent::Parsed { tree: old_tree, .. }) =
|
||||
old_layer.map(|layer| &layer.content)
|
||||
if let Some((SyntaxLayerContent::Parsed { tree: old_tree, .. }, layer_start)) =
|
||||
old_layer.map(|layer| (&layer.content, layer.range.start))
|
||||
{
|
||||
log::trace!(
|
||||
"existing layer. language:{}, start:{:?}, ranges:{:?}",
|
||||
language.name(),
|
||||
LogPoint(layer_start.to_point(&text)),
|
||||
LogIncludedRanges(&old_tree.included_ranges())
|
||||
);
|
||||
|
||||
if let ParseMode::Combined {
|
||||
mut parent_layer_changed_ranges,
|
||||
..
|
||||
} = step.mode
|
||||
{
|
||||
for range in &mut parent_layer_changed_ranges {
|
||||
range.start -= step_start_byte;
|
||||
range.end -= step_start_byte;
|
||||
range.start = range.start.saturating_sub(step_start_byte);
|
||||
range.end = range.end.saturating_sub(step_start_byte);
|
||||
}
|
||||
|
||||
included_ranges = splice_included_ranges(
|
||||
|
@ -570,6 +579,22 @@ impl SyntaxSnapshot {
|
|||
);
|
||||
}
|
||||
|
||||
if included_ranges.is_empty() {
|
||||
included_ranges.push(tree_sitter::Range {
|
||||
start_byte: 0,
|
||||
end_byte: 0,
|
||||
start_point: Default::default(),
|
||||
end_point: Default::default(),
|
||||
});
|
||||
}
|
||||
|
||||
log::trace!(
|
||||
"update layer. language:{}, start:{:?}, ranges:{:?}",
|
||||
language.name(),
|
||||
LogAnchorRange(&step.range, text),
|
||||
LogIncludedRanges(&included_ranges),
|
||||
);
|
||||
|
||||
tree = parse_text(
|
||||
grammar,
|
||||
text.as_rope(),
|
||||
|
@ -586,6 +611,22 @@ impl SyntaxSnapshot {
|
|||
}),
|
||||
);
|
||||
} else {
|
||||
if included_ranges.is_empty() {
|
||||
included_ranges.push(tree_sitter::Range {
|
||||
start_byte: 0,
|
||||
end_byte: 0,
|
||||
start_point: Default::default(),
|
||||
end_point: Default::default(),
|
||||
});
|
||||
}
|
||||
|
||||
log::trace!(
|
||||
"create layer. language:{}, range:{:?}, included_ranges:{:?}",
|
||||
language.name(),
|
||||
LogAnchorRange(&step.range, text),
|
||||
LogIncludedRanges(&included_ranges),
|
||||
);
|
||||
|
||||
tree = parse_text(
|
||||
grammar,
|
||||
text.as_rope(),
|
||||
|
@ -613,6 +654,7 @@ impl SyntaxSnapshot {
|
|||
get_injections(
|
||||
config,
|
||||
text,
|
||||
step.range.clone(),
|
||||
tree.root_node_with_offset(
|
||||
step_start_byte,
|
||||
step_start_point.to_ts_point(),
|
||||
|
@ -1117,6 +1159,7 @@ fn parse_text(
|
|||
fn get_injections(
|
||||
config: &InjectionConfig,
|
||||
text: &BufferSnapshot,
|
||||
outer_range: Range<Anchor>,
|
||||
node: Node,
|
||||
language_registry: &Arc<LanguageRegistry>,
|
||||
depth: usize,
|
||||
|
@ -1153,16 +1196,17 @@ fn get_injections(
|
|||
continue;
|
||||
}
|
||||
|
||||
// Avoid duplicate matches if two changed ranges intersect the same injection.
|
||||
let content_range =
|
||||
content_ranges.first().unwrap().start_byte..content_ranges.last().unwrap().end_byte;
|
||||
if let Some((last_pattern_ix, last_range)) = &prev_match {
|
||||
if mat.pattern_index == *last_pattern_ix && content_range == *last_range {
|
||||
|
||||
// Avoid duplicate matches if two changed ranges intersect the same injection.
|
||||
if let Some((prev_pattern_ix, prev_range)) = &prev_match {
|
||||
if mat.pattern_index == *prev_pattern_ix && content_range == *prev_range {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
prev_match = Some((mat.pattern_index, content_range.clone()));
|
||||
|
||||
prev_match = Some((mat.pattern_index, content_range.clone()));
|
||||
let combined = config.patterns[mat.pattern_index].combined;
|
||||
|
||||
let mut language_name = None;
|
||||
|
@ -1218,11 +1262,10 @@ fn get_injections(
|
|||
|
||||
for (language, mut included_ranges) in combined_injection_ranges.drain() {
|
||||
included_ranges.sort_unstable();
|
||||
let range = text.anchor_before(node.start_byte())..text.anchor_after(node.end_byte());
|
||||
queue.push(ParseStep {
|
||||
depth,
|
||||
language: ParseStepLanguage::Loaded { language },
|
||||
range,
|
||||
range: outer_range.clone(),
|
||||
included_ranges,
|
||||
mode: ParseMode::Combined {
|
||||
parent_layer_range: node.start_byte()..node.end_byte(),
|
||||
|
@ -1234,72 +1277,77 @@ fn get_injections(
|
|||
|
||||
pub(crate) fn splice_included_ranges(
|
||||
mut ranges: Vec<tree_sitter::Range>,
|
||||
changed_ranges: &[Range<usize>],
|
||||
removed_ranges: &[Range<usize>],
|
||||
new_ranges: &[tree_sitter::Range],
|
||||
) -> Vec<tree_sitter::Range> {
|
||||
let mut changed_ranges = changed_ranges.into_iter().peekable();
|
||||
let mut new_ranges = new_ranges.into_iter().peekable();
|
||||
let mut removed_ranges = removed_ranges.iter().cloned().peekable();
|
||||
let mut new_ranges = new_ranges.into_iter().cloned().peekable();
|
||||
let mut ranges_ix = 0;
|
||||
loop {
|
||||
let new_range = new_ranges.peek();
|
||||
let mut changed_range = changed_ranges.peek();
|
||||
let next_new_range = new_ranges.peek();
|
||||
let next_removed_range = removed_ranges.peek();
|
||||
|
||||
// Remove ranges that have changed before inserting any new ranges
|
||||
// into those ranges.
|
||||
if let Some((changed, new)) = changed_range.zip(new_range) {
|
||||
if new.end_byte < changed.start {
|
||||
changed_range = None;
|
||||
let (remove, insert) = match (next_removed_range, next_new_range) {
|
||||
(None, None) => break,
|
||||
(Some(_), None) => (removed_ranges.next().unwrap(), None),
|
||||
(Some(next_removed_range), Some(next_new_range)) => {
|
||||
if next_removed_range.end < next_new_range.start_byte {
|
||||
(removed_ranges.next().unwrap(), None)
|
||||
} else {
|
||||
let mut start = next_new_range.start_byte;
|
||||
let mut end = next_new_range.end_byte;
|
||||
|
||||
while let Some(next_removed_range) = removed_ranges.peek() {
|
||||
if next_removed_range.start > next_new_range.end_byte {
|
||||
break;
|
||||
}
|
||||
let next_removed_range = removed_ranges.next().unwrap();
|
||||
start = cmp::min(start, next_removed_range.start);
|
||||
end = cmp::max(end, next_removed_range.end);
|
||||
}
|
||||
|
||||
(start..end, Some(new_ranges.next().unwrap()))
|
||||
}
|
||||
}
|
||||
(None, Some(next_new_range)) => (
|
||||
next_new_range.start_byte..next_new_range.end_byte,
|
||||
Some(new_ranges.next().unwrap()),
|
||||
),
|
||||
};
|
||||
|
||||
let mut start_ix = ranges_ix
|
||||
+ match ranges[ranges_ix..].binary_search_by_key(&remove.start, |r| r.end_byte) {
|
||||
Ok(ix) => ix,
|
||||
Err(ix) => ix,
|
||||
};
|
||||
let mut end_ix = ranges_ix
|
||||
+ match ranges[ranges_ix..].binary_search_by_key(&remove.end, |r| r.start_byte) {
|
||||
Ok(ix) => ix + 1,
|
||||
Err(ix) => ix,
|
||||
};
|
||||
|
||||
// If there are empty ranges, then there may be multiple ranges with the same
|
||||
// start or end. Expand the splice to include any adjacent ranges that touch
|
||||
// the changed range.
|
||||
while start_ix > 0 {
|
||||
if ranges[start_ix - 1].end_byte == remove.start {
|
||||
start_ix -= 1;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
while let Some(range) = ranges.get(end_ix) {
|
||||
if range.start_byte == remove.end {
|
||||
end_ix += 1;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(changed) = changed_range {
|
||||
let mut start_ix = ranges_ix
|
||||
+ match ranges[ranges_ix..].binary_search_by_key(&changed.start, |r| r.end_byte) {
|
||||
Ok(ix) | Err(ix) => ix,
|
||||
};
|
||||
let mut end_ix = ranges_ix
|
||||
+ match ranges[ranges_ix..].binary_search_by_key(&changed.end, |r| r.start_byte) {
|
||||
Ok(ix) => ix + 1,
|
||||
Err(ix) => ix,
|
||||
};
|
||||
|
||||
// If there are empty ranges, then there may be multiple ranges with the same
|
||||
// start or end. Expand the splice to include any adjacent ranges that touch
|
||||
// the changed range.
|
||||
while start_ix > 0 {
|
||||
if ranges[start_ix - 1].end_byte == changed.start {
|
||||
start_ix -= 1;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
while let Some(range) = ranges.get(end_ix) {
|
||||
if range.start_byte == changed.end {
|
||||
end_ix += 1;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if end_ix > start_ix {
|
||||
ranges.splice(start_ix..end_ix, []);
|
||||
}
|
||||
changed_ranges.next();
|
||||
ranges_ix = start_ix;
|
||||
} else if let Some(new_range) = new_range {
|
||||
let ix = ranges_ix
|
||||
+ match ranges[ranges_ix..]
|
||||
.binary_search_by_key(&new_range.start_byte, |r| r.start_byte)
|
||||
{
|
||||
Ok(ix) | Err(ix) => ix,
|
||||
};
|
||||
ranges.insert(ix, **new_range);
|
||||
new_ranges.next();
|
||||
ranges_ix = ix + 1;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
ranges.splice(start_ix..end_ix, insert);
|
||||
ranges_ix = start_ix;
|
||||
}
|
||||
|
||||
ranges
|
||||
}
|
||||
|
||||
|
@ -1628,3 +1676,46 @@ impl ToTreeSitterPoint for Point {
|
|||
Point::new(point.row as u32, point.column as u32)
|
||||
}
|
||||
}
|
||||
|
||||
struct LogIncludedRanges<'a>(&'a [tree_sitter::Range]);
|
||||
struct LogPoint(Point);
|
||||
struct LogAnchorRange<'a>(&'a Range<Anchor>, &'a text::BufferSnapshot);
|
||||
struct LogChangedRegions<'a>(&'a ChangeRegionSet, &'a text::BufferSnapshot);
|
||||
|
||||
impl<'a> fmt::Debug for LogIncludedRanges<'a> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_list()
|
||||
.entries(self.0.iter().map(|range| {
|
||||
let start = range.start_point;
|
||||
let end = range.end_point;
|
||||
(start.row, start.column)..(end.row, end.column)
|
||||
}))
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> fmt::Debug for LogAnchorRange<'a> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
let range = self.0.to_point(self.1);
|
||||
(LogPoint(range.start)..LogPoint(range.end)).fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> fmt::Debug for LogChangedRegions<'a> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_list()
|
||||
.entries(
|
||||
self.0
|
||||
.0
|
||||
.iter()
|
||||
.map(|region| LogAnchorRange(®ion.range, self.1)),
|
||||
)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for LogPoint {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
(self.0.row, self.0.column).fmt(f)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -48,6 +48,13 @@ fn test_splice_included_ranges() {
|
|||
let new_ranges = splice_included_ranges(ranges.clone(), &[30..50], &[ts_range(25..55)]);
|
||||
assert_eq!(new_ranges, &[ts_range(25..55), ts_range(80..90)]);
|
||||
|
||||
// does not create overlapping ranges
|
||||
let new_ranges = splice_included_ranges(ranges.clone(), &[0..18], &[ts_range(20..32)]);
|
||||
assert_eq!(
|
||||
new_ranges,
|
||||
&[ts_range(20..32), ts_range(50..60), ts_range(80..90)]
|
||||
);
|
||||
|
||||
fn ts_range(range: Range<usize>) -> tree_sitter::Range {
|
||||
tree_sitter::Range {
|
||||
start_byte: range.start,
|
||||
|
@ -624,6 +631,26 @@ fn test_combined_injections_splitting_some_injections() {
|
|||
);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn test_combined_injections_editing_after_last_injection() {
|
||||
test_edit_sequence(
|
||||
"ERB",
|
||||
&[
|
||||
r#"
|
||||
<% foo %>
|
||||
<div></div>
|
||||
<% bar %>
|
||||
"#,
|
||||
r#"
|
||||
<% foo %>
|
||||
<div></div>
|
||||
<% bar %>«
|
||||
more text»
|
||||
"#,
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn test_combined_injections_inside_injections() {
|
||||
let (_buffer, _syntax_map) = test_edit_sequence(
|
||||
|
@ -974,13 +1001,16 @@ fn test_edit_sequence(language_name: &str, steps: &[&str]) -> (Buffer, SyntaxMap
|
|||
mutated_syntax_map.reparse(language.clone(), &buffer);
|
||||
|
||||
for (i, marked_string) in steps.into_iter().enumerate() {
|
||||
buffer.edit_via_marked_text(&marked_string.unindent());
|
||||
let marked_string = marked_string.unindent();
|
||||
log::info!("incremental parse {i}: {marked_string:?}");
|
||||
buffer.edit_via_marked_text(&marked_string);
|
||||
|
||||
// Reparse the syntax map
|
||||
mutated_syntax_map.interpolate(&buffer);
|
||||
mutated_syntax_map.reparse(language.clone(), &buffer);
|
||||
|
||||
// Create a second syntax map from scratch
|
||||
log::info!("fresh parse {i}: {marked_string:?}");
|
||||
let mut reference_syntax_map = SyntaxMap::new();
|
||||
reference_syntax_map.set_language_registry(registry.clone());
|
||||
reference_syntax_map.reparse(language.clone(), &buffer);
|
||||
|
@ -1133,6 +1163,7 @@ fn range_for_text(buffer: &Buffer, text: &str) -> Range<usize> {
|
|||
start..start + text.len()
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
fn assert_layers_for_range(
|
||||
syntax_map: &SyntaxMap,
|
||||
buffer: &BufferSnapshot,
|
||||
|
|
|
@ -55,7 +55,7 @@ impl View for ActiveBufferLanguage {
|
|||
|
||||
MouseEventHandler::<Self, Self>::new(0, cx, |state, cx| {
|
||||
let theme = &theme::current(cx).workspace.status_bar;
|
||||
let style = theme.active_language.style_for(state, false);
|
||||
let style = theme.active_language.style_for(state);
|
||||
Label::new(active_language_text, style.text.clone())
|
||||
.contained()
|
||||
.with_style(style.container)
|
||||
|
|
|
@ -180,7 +180,7 @@ impl PickerDelegate for LanguageSelectorDelegate {
|
|||
) -> AnyElement<Picker<Self>> {
|
||||
let theme = theme::current(cx);
|
||||
let mat = &self.matches[ix];
|
||||
let style = theme.picker.item.style_for(mouse_state, selected);
|
||||
let style = theme.picker.item.in_state(selected).style_for(mouse_state);
|
||||
let buffer_language_name = self.buffer.read(cx).language().map(|l| l.name());
|
||||
let mut label = mat.string.clone();
|
||||
if buffer_language_name.as_deref() == Some(mat.string.as_str()) {
|
||||
|
|
|
@ -681,7 +681,7 @@ impl LspLogToolbarItemView {
|
|||
)
|
||||
})
|
||||
.unwrap_or_else(|| "No server selected".into());
|
||||
let style = theme.toolbar_dropdown_menu.header.style_for(state, false);
|
||||
let style = theme.toolbar_dropdown_menu.header.style_for(state);
|
||||
Label::new(label, style.text.clone())
|
||||
.contained()
|
||||
.with_style(style.container)
|
||||
|
@ -722,7 +722,8 @@ impl LspLogToolbarItemView {
|
|||
let style = theme
|
||||
.toolbar_dropdown_menu
|
||||
.item
|
||||
.style_for(state, logs_selected);
|
||||
.in_state(logs_selected)
|
||||
.style_for(state);
|
||||
Label::new(SERVER_LOGS, style.text.clone())
|
||||
.contained()
|
||||
.with_style(style.container)
|
||||
|
@ -739,7 +740,8 @@ impl LspLogToolbarItemView {
|
|||
let style = theme
|
||||
.toolbar_dropdown_menu
|
||||
.item
|
||||
.style_for(state, rpc_trace_selected);
|
||||
.in_state(rpc_trace_selected)
|
||||
.style_for(state);
|
||||
Flex::row()
|
||||
.with_child(
|
||||
Label::new(RPC_MESSAGES, style.text.clone())
|
||||
|
|
|
@ -565,7 +565,7 @@ impl SyntaxTreeToolbarItemView {
|
|||
) -> impl Element<Self> {
|
||||
enum ToggleMenu {}
|
||||
MouseEventHandler::<ToggleMenu, Self>::new(0, cx, move |state, _| {
|
||||
let style = theme.toolbar_dropdown_menu.header.style_for(state, false);
|
||||
let style = theme.toolbar_dropdown_menu.header.style_for(state);
|
||||
Flex::row()
|
||||
.with_child(
|
||||
Label::new(active_layer.language.name().to_string(), style.text.clone())
|
||||
|
@ -601,7 +601,8 @@ impl SyntaxTreeToolbarItemView {
|
|||
let style = theme
|
||||
.toolbar_dropdown_menu
|
||||
.item
|
||||
.style_for(state, is_selected);
|
||||
.in_state(is_selected)
|
||||
.style_for(state);
|
||||
Flex::row()
|
||||
.with_child(
|
||||
Label::new(layer.language.name().to_string(), style.text.clone())
|
||||
|
|
|
@ -204,7 +204,7 @@ impl PickerDelegate for OutlineViewDelegate {
|
|||
cx: &AppContext,
|
||||
) -> AnyElement<Picker<Self>> {
|
||||
let theme = theme::current(cx);
|
||||
let style = theme.picker.item.style_for(mouse_state, selected);
|
||||
let style = theme.picker.item.in_state(selected).style_for(mouse_state);
|
||||
let string_match = &self.matches[ix];
|
||||
let outline_item = &self.outline.items[string_match.candidate_id];
|
||||
|
||||
|
|
|
@ -153,6 +153,7 @@ pub fn init(cx: &mut AppContext) {
|
|||
);
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Event {
|
||||
OpenedEntry {
|
||||
entry_id: ProjectEntryId,
|
||||
|
@ -1253,7 +1254,10 @@ impl ProjectPanel {
|
|||
let show_editor = details.is_editing && !details.is_processing;
|
||||
|
||||
MouseEventHandler::<Self, _>::new(entry_id.to_usize(), cx, |state, cx| {
|
||||
let mut style = entry_style.style_for(state, details.is_selected).clone();
|
||||
let mut style = entry_style
|
||||
.in_state(details.is_selected)
|
||||
.style_for(state)
|
||||
.clone();
|
||||
|
||||
if cx
|
||||
.global::<DragAndDrop<Workspace>>()
|
||||
|
@ -1264,7 +1268,7 @@ impl ProjectPanel {
|
|||
.filter(|destination| details.path.starts_with(destination))
|
||||
.is_some()
|
||||
{
|
||||
style = entry_style.active.clone().unwrap();
|
||||
style = entry_style.active_state().default.clone();
|
||||
}
|
||||
|
||||
let row_container_style = if show_editor {
|
||||
|
@ -1405,9 +1409,11 @@ impl View for ProjectPanel {
|
|||
let button_style = theme.open_project_button.clone();
|
||||
let context_menu_item_style = theme::current(cx).context_menu.item.clone();
|
||||
move |state, cx| {
|
||||
let button_style = button_style.style_for(state, false).clone();
|
||||
let context_menu_item =
|
||||
context_menu_item_style.style_for(state, true).clone();
|
||||
let button_style = button_style.style_for(state).clone();
|
||||
let context_menu_item = context_menu_item_style
|
||||
.active_state()
|
||||
.style_for(state)
|
||||
.clone();
|
||||
|
||||
theme::ui::keystroke_label(
|
||||
"Open a project",
|
||||
|
|
|
@ -196,7 +196,7 @@ impl PickerDelegate for ProjectSymbolsDelegate {
|
|||
) -> AnyElement<Picker<Self>> {
|
||||
let theme = theme::current(cx);
|
||||
let style = &theme.picker.item;
|
||||
let current_style = style.style_for(mouse_state, selected);
|
||||
let current_style = style.in_state(selected).style_for(mouse_state);
|
||||
|
||||
let string_match = &self.matches[ix];
|
||||
let symbol = &self.symbols[string_match.candidate_id];
|
||||
|
@ -229,7 +229,10 @@ impl PickerDelegate for ProjectSymbolsDelegate {
|
|||
.with_child(
|
||||
// Avoid styling the path differently when it is selected, since
|
||||
// the symbol's syntax highlighting doesn't change when selected.
|
||||
Label::new(path.to_string(), style.default.label.clone()),
|
||||
Label::new(
|
||||
path.to_string(),
|
||||
style.inactive_state().default.label.clone(),
|
||||
),
|
||||
)
|
||||
.contained()
|
||||
.with_style(current_style.container)
|
||||
|
|
|
@ -173,7 +173,7 @@ impl PickerDelegate for RecentProjectsDelegate {
|
|||
cx: &gpui::AppContext,
|
||||
) -> AnyElement<Picker<Self>> {
|
||||
let theme = theme::current(cx);
|
||||
let style = theme.picker.item.style_for(mouse_state, selected);
|
||||
let style = theme.picker.item.in_state(selected).style_for(mouse_state);
|
||||
|
||||
let string_match = &self.matches[ix];
|
||||
|
||||
|
|
|
@ -259,7 +259,11 @@ impl BufferSearchBar {
|
|||
}
|
||||
}
|
||||
|
||||
fn dismiss(&mut self, _: &Dismiss, cx: &mut ViewContext<Self>) {
|
||||
pub fn is_dismissed(&self) -> bool {
|
||||
self.dismissed
|
||||
}
|
||||
|
||||
pub fn dismiss(&mut self, _: &Dismiss, cx: &mut ViewContext<Self>) {
|
||||
self.dismissed = true;
|
||||
for searchable_item in self.seachable_items_with_matches.keys() {
|
||||
if let Some(searchable_item) =
|
||||
|
@ -275,7 +279,7 @@ impl BufferSearchBar {
|
|||
cx.notify();
|
||||
}
|
||||
|
||||
fn show(&mut self, focus: bool, suggest_query: bool, cx: &mut ViewContext<Self>) -> bool {
|
||||
pub fn show(&mut self, focus: bool, suggest_query: bool, cx: &mut ViewContext<Self>) -> bool {
|
||||
let searchable_item = if let Some(searchable_item) = &self.active_searchable_item {
|
||||
SearchableItemHandle::boxed_clone(searchable_item.as_ref())
|
||||
} else {
|
||||
|
@ -328,7 +332,11 @@ impl BufferSearchBar {
|
|||
Some(
|
||||
MouseEventHandler::<Self, _>::new(option as usize, cx, |state, cx| {
|
||||
let theme = theme::current(cx);
|
||||
let style = theme.search.option_button.style_for(state, is_active);
|
||||
let style = theme
|
||||
.search
|
||||
.option_button
|
||||
.in_state(is_active)
|
||||
.style_for(state);
|
||||
Label::new(icon, style.text.clone())
|
||||
.contained()
|
||||
.with_style(style.container)
|
||||
|
@ -371,7 +379,7 @@ impl BufferSearchBar {
|
|||
enum NavButton {}
|
||||
MouseEventHandler::<NavButton, _>::new(direction as usize, cx, |state, cx| {
|
||||
let theme = theme::current(cx);
|
||||
let style = theme.search.option_button.style_for(state, false);
|
||||
let style = theme.search.option_button.inactive_state().style_for(state);
|
||||
Label::new(icon, style.text.clone())
|
||||
.contained()
|
||||
.with_style(style.container)
|
||||
|
@ -403,7 +411,7 @@ impl BufferSearchBar {
|
|||
|
||||
enum CloseButton {}
|
||||
MouseEventHandler::<CloseButton, _>::new(0, cx, |state, _| {
|
||||
let style = theme.dismiss_button.style_for(state, false);
|
||||
let style = theme.dismiss_button.style_for(state);
|
||||
Svg::new("icons/x_mark_8.svg")
|
||||
.with_color(style.color)
|
||||
.constrained()
|
||||
|
@ -480,7 +488,7 @@ impl BufferSearchBar {
|
|||
self.select_match(Direction::Prev, cx);
|
||||
}
|
||||
|
||||
fn select_match(&mut self, direction: Direction, cx: &mut ViewContext<Self>) {
|
||||
pub fn select_match(&mut self, direction: Direction, cx: &mut ViewContext<Self>) {
|
||||
if let Some(index) = self.active_match_index {
|
||||
if let Some(searchable_item) = self.active_searchable_item.as_ref() {
|
||||
if let Some(matches) = self
|
||||
|
|
|
@ -896,7 +896,7 @@ impl ProjectSearchBar {
|
|||
enum NavButton {}
|
||||
MouseEventHandler::<NavButton, _>::new(direction as usize, cx, |state, cx| {
|
||||
let theme = theme::current(cx);
|
||||
let style = theme.search.option_button.style_for(state, false);
|
||||
let style = theme.search.option_button.inactive_state().style_for(state);
|
||||
Label::new(icon, style.text.clone())
|
||||
.contained()
|
||||
.with_style(style.container)
|
||||
|
@ -927,7 +927,11 @@ impl ProjectSearchBar {
|
|||
let is_active = self.is_option_enabled(option, cx);
|
||||
MouseEventHandler::<Self, _>::new(option as usize, cx, |state, cx| {
|
||||
let theme = theme::current(cx);
|
||||
let style = theme.search.option_button.style_for(state, is_active);
|
||||
let style = theme
|
||||
.search
|
||||
.option_button
|
||||
.in_state(is_active)
|
||||
.style_for(state);
|
||||
Label::new(icon, style.text.clone())
|
||||
.contained()
|
||||
.with_style(style.container)
|
||||
|
|
|
@ -25,6 +25,7 @@ pub fn init(cx: &mut AppContext) {
|
|||
cx.add_action(TerminalPanel::new_terminal);
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Event {
|
||||
Close,
|
||||
DockPositionChanged,
|
||||
|
|
|
@ -4,7 +4,7 @@ pub mod ui;
|
|||
|
||||
use gpui::{
|
||||
color::Color,
|
||||
elements::{ContainerStyle, ImageStyle, LabelStyle, Shadow, TooltipStyle},
|
||||
elements::{ContainerStyle, ImageStyle, LabelStyle, Shadow, SvgStyle, TooltipStyle},
|
||||
fonts::{HighlightStyle, TextStyle},
|
||||
platform, AppContext, AssetSource, Border, MouseState,
|
||||
};
|
||||
|
@ -12,7 +12,7 @@ use serde::{de::DeserializeOwned, Deserialize};
|
|||
use serde_json::Value;
|
||||
use settings::SettingsStore;
|
||||
use std::{collections::HashMap, sync::Arc};
|
||||
use ui::{ButtonStyle, CheckboxStyle, IconStyle, ModalStyle, SvgStyle};
|
||||
use ui::{ButtonStyle, CheckboxStyle, IconStyle, ModalStyle};
|
||||
|
||||
pub use theme_registry::*;
|
||||
pub use theme_settings::*;
|
||||
|
@ -128,12 +128,12 @@ pub struct Titlebar {
|
|||
pub leader_avatar: AvatarStyle,
|
||||
pub follower_avatar: AvatarStyle,
|
||||
pub inactive_avatar_grayscale: bool,
|
||||
pub sign_in_prompt: Interactive<ContainedText>,
|
||||
pub sign_in_prompt: Toggleable<Interactive<ContainedText>>,
|
||||
pub outdated_warning: ContainedText,
|
||||
pub share_button: Interactive<ContainedText>,
|
||||
pub share_button: Toggleable<Interactive<ContainedText>>,
|
||||
pub call_control: Interactive<IconButton>,
|
||||
pub toggle_contacts_button: Interactive<IconButton>,
|
||||
pub user_menu_button: Interactive<IconButton>,
|
||||
pub toggle_contacts_button: Toggleable<Interactive<IconButton>>,
|
||||
pub user_menu_button: Toggleable<Interactive<IconButton>>,
|
||||
pub toggle_contacts_badge: ContainerStyle,
|
||||
}
|
||||
|
||||
|
@ -204,12 +204,12 @@ pub struct ContactList {
|
|||
pub user_query_editor: FieldEditor,
|
||||
pub user_query_editor_height: f32,
|
||||
pub add_contact_button: IconButton,
|
||||
pub header_row: Interactive<ContainedText>,
|
||||
pub header_row: Toggleable<Interactive<ContainedText>>,
|
||||
pub leave_call: Interactive<ContainedText>,
|
||||
pub contact_row: Interactive<ContainerStyle>,
|
||||
pub contact_row: Toggleable<Interactive<ContainerStyle>>,
|
||||
pub row_height: f32,
|
||||
pub project_row: Interactive<ProjectRow>,
|
||||
pub tree_branch: Interactive<TreeBranch>,
|
||||
pub project_row: Toggleable<Interactive<ProjectRow>>,
|
||||
pub tree_branch: Toggleable<Interactive<TreeBranch>>,
|
||||
pub contact_avatar: ImageStyle,
|
||||
pub contact_status_free: ContainerStyle,
|
||||
pub contact_status_busy: ContainerStyle,
|
||||
|
@ -251,7 +251,7 @@ pub struct DropdownMenu {
|
|||
pub container: ContainerStyle,
|
||||
pub header: Interactive<DropdownMenuItem>,
|
||||
pub section_header: ContainedText,
|
||||
pub item: Interactive<DropdownMenuItem>,
|
||||
pub item: Toggleable<Interactive<DropdownMenuItem>>,
|
||||
pub row_height: f32,
|
||||
}
|
||||
|
||||
|
@ -270,7 +270,7 @@ pub struct DropdownMenuItem {
|
|||
pub struct TabBar {
|
||||
#[serde(flatten)]
|
||||
pub container: ContainerStyle,
|
||||
pub pane_button: Interactive<IconButton>,
|
||||
pub pane_button: Toggleable<Interactive<IconButton>>,
|
||||
pub pane_button_container: ContainerStyle,
|
||||
pub active_pane: TabStyles,
|
||||
pub inactive_pane: TabStyles,
|
||||
|
@ -359,7 +359,7 @@ pub struct Search {
|
|||
pub include_exclude_editor: FindEditor,
|
||||
pub invalid_include_exclude_editor: ContainerStyle,
|
||||
pub include_exclude_inputs: ContainedText,
|
||||
pub option_button: Interactive<ContainedText>,
|
||||
pub option_button: Toggleable<Interactive<ContainedText>>,
|
||||
pub match_background: Color,
|
||||
pub match_index: ContainedText,
|
||||
pub results_status: TextStyle,
|
||||
|
@ -395,7 +395,7 @@ pub struct StatusBarPanelButtons {
|
|||
pub group_left: ContainerStyle,
|
||||
pub group_bottom: ContainerStyle,
|
||||
pub group_right: ContainerStyle,
|
||||
pub button: Interactive<PanelButton>,
|
||||
pub button: Toggleable<Interactive<PanelButton>>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Default)]
|
||||
|
@ -444,10 +444,10 @@ pub struct PanelButton {
|
|||
pub struct ProjectPanel {
|
||||
#[serde(flatten)]
|
||||
pub container: ContainerStyle,
|
||||
pub entry: Interactive<ProjectPanelEntry>,
|
||||
pub entry: Toggleable<Interactive<ProjectPanelEntry>>,
|
||||
pub dragged_entry: ProjectPanelEntry,
|
||||
pub ignored_entry: Interactive<ProjectPanelEntry>,
|
||||
pub cut_entry: Interactive<ProjectPanelEntry>,
|
||||
pub ignored_entry: Toggleable<Interactive<ProjectPanelEntry>>,
|
||||
pub cut_entry: Toggleable<Interactive<ProjectPanelEntry>>,
|
||||
pub filename_editor: FieldEditor,
|
||||
pub indent_width: f32,
|
||||
pub open_project_button: Interactive<ContainedText>,
|
||||
|
@ -481,7 +481,7 @@ pub struct GitProjectStatus {
|
|||
pub struct ContextMenu {
|
||||
#[serde(flatten)]
|
||||
pub container: ContainerStyle,
|
||||
pub item: Interactive<ContextMenuItem>,
|
||||
pub item: Toggleable<Interactive<ContextMenuItem>>,
|
||||
pub keystroke_margin: f32,
|
||||
pub separator: ContainerStyle,
|
||||
}
|
||||
|
@ -498,7 +498,7 @@ pub struct ContextMenuItem {
|
|||
|
||||
#[derive(Debug, Deserialize, Default)]
|
||||
pub struct CommandPalette {
|
||||
pub key: Interactive<ContainedLabel>,
|
||||
pub key: Toggleable<ContainedLabel>,
|
||||
pub keystroke_spacing: f32,
|
||||
}
|
||||
|
||||
|
@ -565,7 +565,7 @@ pub struct Picker {
|
|||
pub input_editor: FieldEditor,
|
||||
pub empty_input_editor: FieldEditor,
|
||||
pub no_matches: ContainedLabel,
|
||||
pub item: Interactive<ContainedLabel>,
|
||||
pub item: Toggleable<Interactive<ContainedLabel>>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Default)]
|
||||
|
@ -771,13 +771,13 @@ pub struct InteractiveColor {
|
|||
#[derive(Clone, Deserialize, Default)]
|
||||
pub struct CodeActions {
|
||||
#[serde(default)]
|
||||
pub indicator: Interactive<InteractiveColor>,
|
||||
pub indicator: Toggleable<Interactive<InteractiveColor>>,
|
||||
pub vertical_scale: f32,
|
||||
}
|
||||
|
||||
#[derive(Clone, Deserialize, Default)]
|
||||
pub struct Folds {
|
||||
pub indicator: Interactive<InteractiveColor>,
|
||||
pub indicator: Toggleable<Interactive<InteractiveColor>>,
|
||||
pub ellipses: FoldEllipses,
|
||||
pub fold_background: Color,
|
||||
pub icon_margin_scale: f32,
|
||||
|
@ -805,38 +805,46 @@ pub struct DiffStyle {
|
|||
#[derive(Debug, Default, Clone, Copy)]
|
||||
pub struct Interactive<T> {
|
||||
pub default: T,
|
||||
pub hover: Option<T>,
|
||||
pub hover_and_active: Option<T>,
|
||||
pub hovered: Option<T>,
|
||||
pub clicked: Option<T>,
|
||||
pub click_and_active: Option<T>,
|
||||
pub active: Option<T>,
|
||||
pub disabled: Option<T>,
|
||||
}
|
||||
|
||||
impl<T> Interactive<T> {
|
||||
pub fn style_for(&self, state: &mut MouseState, active: bool) -> &T {
|
||||
#[derive(Clone, Copy, Debug, Default, Deserialize)]
|
||||
pub struct Toggleable<T> {
|
||||
active: T,
|
||||
inactive: T,
|
||||
}
|
||||
|
||||
impl<T> Toggleable<T> {
|
||||
pub fn new(active: T, inactive: T) -> Self {
|
||||
Self { active, inactive }
|
||||
}
|
||||
pub fn in_state(&self, active: bool) -> &T {
|
||||
if active {
|
||||
if state.hovered() {
|
||||
self.hover_and_active
|
||||
.as_ref()
|
||||
.unwrap_or(self.active.as_ref().unwrap_or(&self.default))
|
||||
} else if state.clicked() == Some(platform::MouseButton::Left) && self.clicked.is_some()
|
||||
{
|
||||
self.click_and_active
|
||||
.as_ref()
|
||||
.unwrap_or(self.active.as_ref().unwrap_or(&self.default))
|
||||
} else {
|
||||
self.active.as_ref().unwrap_or(&self.default)
|
||||
}
|
||||
} else if state.clicked() == Some(platform::MouseButton::Left) && self.clicked.is_some() {
|
||||
&self.active
|
||||
} else {
|
||||
&self.inactive
|
||||
}
|
||||
}
|
||||
pub fn active_state(&self) -> &T {
|
||||
self.in_state(true)
|
||||
}
|
||||
pub fn inactive_state(&self) -> &T {
|
||||
self.in_state(false)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Interactive<T> {
|
||||
pub fn style_for(&self, state: &mut MouseState) -> &T {
|
||||
if state.clicked() == Some(platform::MouseButton::Left) && self.clicked.is_some() {
|
||||
self.clicked.as_ref().unwrap()
|
||||
} else if state.hovered() {
|
||||
self.hover.as_ref().unwrap_or(&self.default)
|
||||
self.hovered.as_ref().unwrap_or(&self.default)
|
||||
} else {
|
||||
&self.default
|
||||
}
|
||||
}
|
||||
|
||||
pub fn disabled_style(&self) -> &T {
|
||||
self.disabled.as_ref().unwrap_or(&self.default)
|
||||
}
|
||||
|
@ -849,13 +857,9 @@ impl<'de, T: DeserializeOwned> Deserialize<'de> for Interactive<T> {
|
|||
{
|
||||
#[derive(Deserialize)]
|
||||
struct Helper {
|
||||
#[serde(flatten)]
|
||||
default: Value,
|
||||
hover: Option<Value>,
|
||||
hover_and_active: Option<Value>,
|
||||
hovered: Option<Value>,
|
||||
clicked: Option<Value>,
|
||||
click_and_active: Option<Value>,
|
||||
active: Option<Value>,
|
||||
disabled: Option<Value>,
|
||||
}
|
||||
|
||||
|
@ -880,21 +884,15 @@ impl<'de, T: DeserializeOwned> Deserialize<'de> for Interactive<T> {
|
|||
}
|
||||
};
|
||||
|
||||
let hover = deserialize_state(json.hover)?;
|
||||
let hover_and_active = deserialize_state(json.hover_and_active)?;
|
||||
let hovered = deserialize_state(json.hovered)?;
|
||||
let clicked = deserialize_state(json.clicked)?;
|
||||
let click_and_active = deserialize_state(json.click_and_active)?;
|
||||
let active = deserialize_state(json.active)?;
|
||||
let disabled = deserialize_state(json.disabled)?;
|
||||
let default = serde_json::from_value(json.default).map_err(serde::de::Error::custom)?;
|
||||
|
||||
Ok(Interactive {
|
||||
default,
|
||||
hover,
|
||||
hover_and_active,
|
||||
hovered,
|
||||
clicked,
|
||||
click_and_active,
|
||||
active,
|
||||
disabled,
|
||||
})
|
||||
}
|
||||
|
@ -994,18 +992,33 @@ pub struct TerminalStyle {
|
|||
#[derive(Clone, Deserialize, Default)]
|
||||
pub struct AssistantStyle {
|
||||
pub container: ContainerStyle,
|
||||
pub header: ContainerStyle,
|
||||
pub hamburger_button: Interactive<IconStyle>,
|
||||
pub split_button: Interactive<IconStyle>,
|
||||
pub assist_button: Interactive<IconStyle>,
|
||||
pub quote_button: Interactive<IconStyle>,
|
||||
pub zoom_in_button: Interactive<IconStyle>,
|
||||
pub zoom_out_button: Interactive<IconStyle>,
|
||||
pub plus_button: Interactive<IconStyle>,
|
||||
pub title: ContainedText,
|
||||
pub message_header: ContainerStyle,
|
||||
pub sent_at: ContainedText,
|
||||
pub user_sender: Interactive<ContainedText>,
|
||||
pub assistant_sender: Interactive<ContainedText>,
|
||||
pub system_sender: Interactive<ContainedText>,
|
||||
pub model_info_container: ContainerStyle,
|
||||
pub model: Interactive<ContainedText>,
|
||||
pub remaining_tokens: ContainedText,
|
||||
pub no_remaining_tokens: ContainedText,
|
||||
pub error_icon: Icon,
|
||||
pub api_key_editor: FieldEditor,
|
||||
pub api_key_prompt: ContainedText,
|
||||
pub saved_conversation: SavedConversation,
|
||||
}
|
||||
|
||||
#[derive(Clone, Deserialize, Default)]
|
||||
pub struct SavedConversation {
|
||||
pub container: Interactive<ContainerStyle>,
|
||||
pub saved_at: ContainedText,
|
||||
pub title: ContainedText,
|
||||
}
|
||||
|
||||
#[derive(Clone, Deserialize, Default)]
|
||||
|
|
|
@ -1,13 +1,12 @@
|
|||
use std::borrow::Cow;
|
||||
|
||||
use gpui::{
|
||||
color::Color,
|
||||
elements::{
|
||||
ConstrainedBox, Container, ContainerStyle, Empty, Flex, KeystrokeLabel, Label,
|
||||
MouseEventHandler, ParentElement, Stack, Svg,
|
||||
ConstrainedBox, Container, ContainerStyle, Dimensions, Empty, Flex, KeystrokeLabel, Label,
|
||||
MouseEventHandler, ParentElement, Stack, Svg, SvgStyle,
|
||||
},
|
||||
fonts::TextStyle,
|
||||
geometry::vector::{vec2f, Vector2F},
|
||||
geometry::vector::Vector2F,
|
||||
platform,
|
||||
platform::MouseButton,
|
||||
scene::MouseClick,
|
||||
|
@ -93,25 +92,6 @@ where
|
|||
.with_cursor_style(platform::CursorStyle::PointingHand)
|
||||
}
|
||||
|
||||
#[derive(Clone, Deserialize, Default)]
|
||||
pub struct SvgStyle {
|
||||
pub color: Color,
|
||||
pub asset: String,
|
||||
pub dimensions: Dimensions,
|
||||
}
|
||||
|
||||
#[derive(Clone, Deserialize, Default)]
|
||||
pub struct Dimensions {
|
||||
pub width: f32,
|
||||
pub height: f32,
|
||||
}
|
||||
|
||||
impl Dimensions {
|
||||
pub fn to_vec(&self) -> Vector2F {
|
||||
vec2f(self.width, self.height)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn svg<V: View>(style: &SvgStyle) -> ConstrainedBox<V> {
|
||||
Svg::new(style.asset.clone())
|
||||
.with_color(style.color)
|
||||
|
@ -122,8 +102,8 @@ pub fn svg<V: View>(style: &SvgStyle) -> ConstrainedBox<V> {
|
|||
|
||||
#[derive(Clone, Deserialize, Default)]
|
||||
pub struct IconStyle {
|
||||
icon: SvgStyle,
|
||||
container: ContainerStyle,
|
||||
pub icon: SvgStyle,
|
||||
pub container: ContainerStyle,
|
||||
}
|
||||
|
||||
pub fn icon<V: View>(style: &IconStyle) -> Container<V> {
|
||||
|
@ -170,7 +150,7 @@ where
|
|||
F: Fn(MouseClick, &mut V, &mut EventContext<V>) + 'static,
|
||||
{
|
||||
MouseEventHandler::<Tag, V>::new(0, cx, |state, _| {
|
||||
let style = style.style_for(state, false);
|
||||
let style = style.style_for(state);
|
||||
Label::new(label, style.text.to_owned())
|
||||
.aligned()
|
||||
.contained()
|
||||
|
@ -220,13 +200,13 @@ where
|
|||
title,
|
||||
style
|
||||
.title_text
|
||||
.style_for(&mut MouseState::default(), false)
|
||||
.style_for(&mut MouseState::default())
|
||||
.clone(),
|
||||
))
|
||||
.with_child(
|
||||
// FIXME: Get a better tag type
|
||||
MouseEventHandler::<Tag, V>::new(999999, cx, |state, _cx| {
|
||||
let style = style.close_icon.style_for(state, false);
|
||||
let style = style.close_icon.style_for(state);
|
||||
icon(style)
|
||||
})
|
||||
.on_click(platform::MouseButton::Left, move |_, _, cx| {
|
||||
|
|
|
@ -208,7 +208,7 @@ impl PickerDelegate for ThemeSelectorDelegate {
|
|||
cx: &AppContext,
|
||||
) -> AnyElement<Picker<Self>> {
|
||||
let theme = theme::current(cx);
|
||||
let style = theme.picker.item.style_for(mouse_state, selected);
|
||||
let style = theme.picker.item.in_state(selected).style_for(mouse_state);
|
||||
|
||||
let theme_match = &self.matches[ix];
|
||||
Label::new(theme_match.string.clone(), style.label.clone())
|
||||
|
|
|
@ -1,19 +0,0 @@
|
|||
[package]
|
||||
name = "theme_testbench"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
publish = false
|
||||
|
||||
[lib]
|
||||
path = "src/theme_testbench.rs"
|
||||
doctest = false
|
||||
|
||||
|
||||
[dependencies]
|
||||
gpui = { path = "../gpui" }
|
||||
theme = { path = "../theme" }
|
||||
settings = { path = "../settings" }
|
||||
workspace = { path = "../workspace" }
|
||||
project = { path = "../project" }
|
||||
|
||||
smallvec.workspace = true
|
|
@ -1,300 +0,0 @@
|
|||
use gpui::{
|
||||
actions,
|
||||
color::Color,
|
||||
elements::{
|
||||
AnyElement, Canvas, Container, ContainerStyle, Flex, Label, Margin, MouseEventHandler,
|
||||
Padding, ParentElement,
|
||||
},
|
||||
fonts::TextStyle,
|
||||
AppContext, Border, Element, Entity, ModelHandle, Quad, Task, View, ViewContext, ViewHandle,
|
||||
WeakViewHandle,
|
||||
};
|
||||
use project::Project;
|
||||
use theme::{ColorScheme, Layer, Style, StyleSet, ThemeSettings};
|
||||
use workspace::{item::Item, register_deserializable_item, Pane, Workspace};
|
||||
|
||||
actions!(theme, [DeployThemeTestbench]);
|
||||
|
||||
pub fn init(cx: &mut AppContext) {
|
||||
cx.add_action(ThemeTestbench::deploy);
|
||||
|
||||
register_deserializable_item::<ThemeTestbench>(cx)
|
||||
}
|
||||
|
||||
pub struct ThemeTestbench {}
|
||||
|
||||
impl ThemeTestbench {
|
||||
pub fn deploy(
|
||||
workspace: &mut Workspace,
|
||||
_: &DeployThemeTestbench,
|
||||
cx: &mut ViewContext<Workspace>,
|
||||
) {
|
||||
let view = cx.add_view(|_| ThemeTestbench {});
|
||||
workspace.add_item(Box::new(view), cx);
|
||||
}
|
||||
|
||||
fn render_ramps(color_scheme: &ColorScheme) -> Flex<Self> {
|
||||
fn display_ramp(ramp: &Vec<Color>) -> AnyElement<ThemeTestbench> {
|
||||
Flex::row()
|
||||
.with_children(ramp.iter().cloned().map(|color| {
|
||||
Canvas::new(move |scene, bounds, _, _, _| {
|
||||
scene.push_quad(Quad {
|
||||
bounds,
|
||||
background: Some(color),
|
||||
..Default::default()
|
||||
});
|
||||
})
|
||||
.flex(1.0, false)
|
||||
}))
|
||||
.flex(1.0, false)
|
||||
.into_any()
|
||||
}
|
||||
|
||||
Flex::column()
|
||||
.with_child(display_ramp(&color_scheme.ramps.neutral))
|
||||
.with_child(display_ramp(&color_scheme.ramps.red))
|
||||
.with_child(display_ramp(&color_scheme.ramps.orange))
|
||||
.with_child(display_ramp(&color_scheme.ramps.yellow))
|
||||
.with_child(display_ramp(&color_scheme.ramps.green))
|
||||
.with_child(display_ramp(&color_scheme.ramps.cyan))
|
||||
.with_child(display_ramp(&color_scheme.ramps.blue))
|
||||
.with_child(display_ramp(&color_scheme.ramps.violet))
|
||||
.with_child(display_ramp(&color_scheme.ramps.magenta))
|
||||
}
|
||||
|
||||
fn render_layer(
|
||||
layer_index: usize,
|
||||
layer: &Layer,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) -> Container<Self> {
|
||||
Flex::column()
|
||||
.with_child(
|
||||
Self::render_button_set(0, layer_index, "base", &layer.base, cx).flex(1., false),
|
||||
)
|
||||
.with_child(
|
||||
Self::render_button_set(1, layer_index, "variant", &layer.variant, cx)
|
||||
.flex(1., false),
|
||||
)
|
||||
.with_child(
|
||||
Self::render_button_set(2, layer_index, "on", &layer.on, cx).flex(1., false),
|
||||
)
|
||||
.with_child(
|
||||
Self::render_button_set(3, layer_index, "accent", &layer.accent, cx)
|
||||
.flex(1., false),
|
||||
)
|
||||
.with_child(
|
||||
Self::render_button_set(4, layer_index, "positive", &layer.positive, cx)
|
||||
.flex(1., false),
|
||||
)
|
||||
.with_child(
|
||||
Self::render_button_set(5, layer_index, "warning", &layer.warning, cx)
|
||||
.flex(1., false),
|
||||
)
|
||||
.with_child(
|
||||
Self::render_button_set(6, layer_index, "negative", &layer.negative, cx)
|
||||
.flex(1., false),
|
||||
)
|
||||
.contained()
|
||||
.with_style(ContainerStyle {
|
||||
margin: Margin {
|
||||
top: 10.,
|
||||
bottom: 10.,
|
||||
left: 10.,
|
||||
right: 10.,
|
||||
},
|
||||
background_color: Some(layer.base.default.background),
|
||||
..Default::default()
|
||||
})
|
||||
}
|
||||
|
||||
fn render_button_set(
|
||||
set_index: usize,
|
||||
layer_index: usize,
|
||||
set_name: &'static str,
|
||||
style_set: &StyleSet,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) -> Flex<Self> {
|
||||
Flex::row()
|
||||
.with_child(Self::render_button(
|
||||
set_index * 6,
|
||||
layer_index,
|
||||
set_name,
|
||||
&style_set,
|
||||
None,
|
||||
cx,
|
||||
))
|
||||
.with_child(Self::render_button(
|
||||
set_index * 6 + 1,
|
||||
layer_index,
|
||||
"hovered",
|
||||
&style_set,
|
||||
Some(|style_set| &style_set.hovered),
|
||||
cx,
|
||||
))
|
||||
.with_child(Self::render_button(
|
||||
set_index * 6 + 2,
|
||||
layer_index,
|
||||
"pressed",
|
||||
&style_set,
|
||||
Some(|style_set| &style_set.pressed),
|
||||
cx,
|
||||
))
|
||||
.with_child(Self::render_button(
|
||||
set_index * 6 + 3,
|
||||
layer_index,
|
||||
"active",
|
||||
&style_set,
|
||||
Some(|style_set| &style_set.active),
|
||||
cx,
|
||||
))
|
||||
.with_child(Self::render_button(
|
||||
set_index * 6 + 4,
|
||||
layer_index,
|
||||
"disabled",
|
||||
&style_set,
|
||||
Some(|style_set| &style_set.disabled),
|
||||
cx,
|
||||
))
|
||||
.with_child(Self::render_button(
|
||||
set_index * 6 + 5,
|
||||
layer_index,
|
||||
"inverted",
|
||||
&style_set,
|
||||
Some(|style_set| &style_set.inverted),
|
||||
cx,
|
||||
))
|
||||
}
|
||||
|
||||
fn render_button(
|
||||
button_index: usize,
|
||||
layer_index: usize,
|
||||
text: &'static str,
|
||||
style_set: &StyleSet,
|
||||
style_override: Option<fn(&StyleSet) -> &Style>,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) -> AnyElement<Self> {
|
||||
enum TestBenchButton {}
|
||||
MouseEventHandler::<TestBenchButton, _>::new(layer_index + button_index, cx, |state, cx| {
|
||||
let style = if let Some(style_override) = style_override {
|
||||
style_override(&style_set)
|
||||
} else if state.clicked().is_some() {
|
||||
&style_set.pressed
|
||||
} else if state.hovered() {
|
||||
&style_set.hovered
|
||||
} else {
|
||||
&style_set.default
|
||||
};
|
||||
|
||||
Self::render_label(text.to_string(), style, cx)
|
||||
.contained()
|
||||
.with_style(ContainerStyle {
|
||||
margin: Margin {
|
||||
top: 4.,
|
||||
bottom: 4.,
|
||||
left: 4.,
|
||||
right: 4.,
|
||||
},
|
||||
padding: Padding {
|
||||
top: 4.,
|
||||
bottom: 4.,
|
||||
left: 4.,
|
||||
right: 4.,
|
||||
},
|
||||
background_color: Some(style.background),
|
||||
border: Border {
|
||||
width: 1.,
|
||||
color: style.border,
|
||||
overlay: false,
|
||||
top: true,
|
||||
bottom: true,
|
||||
left: true,
|
||||
right: true,
|
||||
},
|
||||
corner_radius: 2.,
|
||||
..Default::default()
|
||||
})
|
||||
})
|
||||
.flex(1., true)
|
||||
.into_any()
|
||||
}
|
||||
|
||||
fn render_label(text: String, style: &Style, cx: &mut ViewContext<Self>) -> Label {
|
||||
let settings = settings::get::<ThemeSettings>(cx);
|
||||
let font_cache = cx.font_cache();
|
||||
let family_id = settings.buffer_font_family;
|
||||
let font_size = settings.buffer_font_size(cx);
|
||||
let font_id = font_cache
|
||||
.select_font(family_id, &Default::default())
|
||||
.unwrap();
|
||||
|
||||
let text_style = TextStyle {
|
||||
color: style.foreground,
|
||||
font_family_id: family_id,
|
||||
font_family_name: font_cache.family_name(family_id).unwrap(),
|
||||
font_id,
|
||||
font_size,
|
||||
font_properties: Default::default(),
|
||||
underline: Default::default(),
|
||||
};
|
||||
|
||||
Label::new(text, text_style)
|
||||
}
|
||||
}
|
||||
|
||||
impl Entity for ThemeTestbench {
|
||||
type Event = ();
|
||||
}
|
||||
|
||||
impl View for ThemeTestbench {
|
||||
fn ui_name() -> &'static str {
|
||||
"ThemeTestbench"
|
||||
}
|
||||
|
||||
fn render(&mut self, cx: &mut gpui::ViewContext<Self>) -> AnyElement<Self> {
|
||||
let color_scheme = &theme::current(cx).clone().color_scheme;
|
||||
|
||||
Flex::row()
|
||||
.with_child(
|
||||
Self::render_ramps(color_scheme)
|
||||
.contained()
|
||||
.with_margin_right(10.)
|
||||
.flex(0.1, false),
|
||||
)
|
||||
.with_child(
|
||||
Flex::column()
|
||||
.with_child(Self::render_layer(100, &color_scheme.lowest, cx).flex(1., true))
|
||||
.with_child(Self::render_layer(200, &color_scheme.middle, cx).flex(1., true))
|
||||
.with_child(Self::render_layer(300, &color_scheme.highest, cx).flex(1., true))
|
||||
.flex(1., false),
|
||||
)
|
||||
.into_any()
|
||||
}
|
||||
}
|
||||
|
||||
impl Item for ThemeTestbench {
|
||||
fn tab_content<T: View>(
|
||||
&self,
|
||||
_: Option<usize>,
|
||||
style: &theme::Tab,
|
||||
_: &AppContext,
|
||||
) -> AnyElement<T> {
|
||||
Label::new("Theme Testbench", style.label.clone())
|
||||
.aligned()
|
||||
.contained()
|
||||
.into_any()
|
||||
}
|
||||
|
||||
fn serialized_item_kind() -> Option<&'static str> {
|
||||
Some("ThemeTestBench")
|
||||
}
|
||||
|
||||
fn deserialize(
|
||||
_project: ModelHandle<Project>,
|
||||
_workspace: WeakViewHandle<Workspace>,
|
||||
_workspace_id: workspace::WorkspaceId,
|
||||
_item_id: workspace::ItemId,
|
||||
cx: &mut ViewContext<Pane>,
|
||||
) -> Task<gpui::anyhow::Result<ViewHandle<Self>>> {
|
||||
Task::ready(Ok(cx.add_view(|_| Self {})))
|
||||
}
|
||||
}
|
|
@ -5,6 +5,7 @@ use serde::{Deserialize, Serialize};
|
|||
lazy_static::lazy_static! {
|
||||
pub static ref HOME: PathBuf = dirs::home_dir().expect("failed to determine home directory");
|
||||
pub static ref CONFIG_DIR: PathBuf = HOME.join(".config").join("zed");
|
||||
pub static ref CONVERSATIONS_DIR: PathBuf = HOME.join(".config/zed/conversations");
|
||||
pub static ref LOGS_DIR: PathBuf = HOME.join("Library/Logs/Zed");
|
||||
pub static ref SUPPORT_DIR: PathBuf = HOME.join("Library/Application Support/Zed");
|
||||
pub static ref LANGUAGES_DIR: PathBuf = HOME.join("Library/Application Support/Zed/languages");
|
||||
|
|
|
@ -141,7 +141,7 @@ impl PickerDelegate for BaseKeymapSelectorDelegate {
|
|||
) -> gpui::AnyElement<Picker<Self>> {
|
||||
let theme = &theme::current(cx);
|
||||
let keymap_match = &self.matches[ix];
|
||||
let style = theme.picker.item.style_for(mouse_state, selected);
|
||||
let style = theme.picker.item.in_state(selected).style_for(mouse_state);
|
||||
|
||||
Label::new(keymap_match.string.clone(), style.label.clone())
|
||||
.with_highlights(keymap_match.positions.clone())
|
||||
|
|
|
@ -249,7 +249,7 @@ impl Dock {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn add_panel<T: Panel>(&mut self, panel: ViewHandle<T>, cx: &mut ViewContext<Self>) {
|
||||
pub(crate) fn add_panel<T: Panel>(&mut self, panel: ViewHandle<T>, cx: &mut ViewContext<Self>) {
|
||||
let subscriptions = [
|
||||
cx.observe(&panel, |_, _, cx| cx.notify()),
|
||||
cx.subscribe(&panel, |this, panel, event, cx| {
|
||||
|
@ -498,7 +498,9 @@ impl View for PanelButtons {
|
|||
Stack::new()
|
||||
.with_child(
|
||||
MouseEventHandler::<Self, _>::new(panel_ix, cx, |state, cx| {
|
||||
let style = button_style.style_for(state, is_active);
|
||||
let style = button_style.in_state(is_active);
|
||||
|
||||
let style = style.style_for(state);
|
||||
Flex::row()
|
||||
.with_child(
|
||||
Svg::new(view.icon_path(cx))
|
||||
|
@ -603,6 +605,7 @@ pub mod test {
|
|||
use super::*;
|
||||
use gpui::{ViewContext, WindowContext};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum TestPanelEvent {
|
||||
PositionChanged,
|
||||
Activated,
|
||||
|
|
|
@ -291,7 +291,7 @@ pub mod simple_message_notification {
|
|||
)
|
||||
.with_child(
|
||||
MouseEventHandler::<Cancel, _>::new(0, cx, |state, _| {
|
||||
let style = theme.dismiss_button.style_for(state, false);
|
||||
let style = theme.dismiss_button.style_for(state);
|
||||
Svg::new("icons/x_mark_8.svg")
|
||||
.with_color(style.color)
|
||||
.constrained()
|
||||
|
@ -323,7 +323,7 @@ pub mod simple_message_notification {
|
|||
0,
|
||||
cx,
|
||||
|state, _| {
|
||||
let style = theme.action_message.style_for(state, false);
|
||||
let style = theme.action_message.style_for(state);
|
||||
|
||||
Flex::row()
|
||||
.with_child(
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
mod dragged_item_receiver;
|
||||
|
||||
use super::{ItemHandle, SplitDirection};
|
||||
pub use crate::toolbar::Toolbar;
|
||||
use crate::{
|
||||
item::WeakItemHandle, notify_of_new_dock, toolbar::Toolbar, AutosaveSetting, Item,
|
||||
NewCenterTerminal, NewFile, NewSearch, ToggleZoom, Workspace, WorkspaceSettings,
|
||||
item::WeakItemHandle, notify_of_new_dock, AutosaveSetting, Item, NewCenterTerminal, NewFile,
|
||||
NewSearch, ToggleZoom, Workspace, WorkspaceSettings,
|
||||
};
|
||||
use anyhow::Result;
|
||||
use collections::{HashMap, HashSet, VecDeque};
|
||||
|
@ -250,7 +251,7 @@ impl Pane {
|
|||
pane: handle.clone(),
|
||||
next_timestamp,
|
||||
}))),
|
||||
toolbar: cx.add_view(|_| Toolbar::new(handle)),
|
||||
toolbar: cx.add_view(|_| Toolbar::new(Some(handle))),
|
||||
tab_bar_context_menu: TabBarContextMenu {
|
||||
kind: TabBarContextMenuKind::New,
|
||||
handle: context_menu,
|
||||
|
@ -285,19 +286,27 @@ impl Pane {
|
|||
pane.tab_bar_context_menu
|
||||
.handle_if_kind(TabBarContextMenuKind::Split),
|
||||
))
|
||||
.with_child(Pane::render_tab_bar_button(
|
||||
2,
|
||||
.with_child({
|
||||
let icon_path;
|
||||
let tooltip_label;
|
||||
if pane.is_zoomed() {
|
||||
"icons/minimize_8.svg"
|
||||
icon_path = "icons/minimize_8.svg";
|
||||
tooltip_label = "Zoom In".into();
|
||||
} else {
|
||||
"icons/maximize_8.svg"
|
||||
},
|
||||
pane.is_zoomed(),
|
||||
Some(("Toggle Zoom".into(), Some(Box::new(ToggleZoom)))),
|
||||
cx,
|
||||
move |pane, cx| pane.toggle_zoom(&Default::default(), cx),
|
||||
None,
|
||||
))
|
||||
icon_path = "icons/maximize_8.svg";
|
||||
tooltip_label = "Zoom In".into();
|
||||
}
|
||||
|
||||
Pane::render_tab_bar_button(
|
||||
2,
|
||||
icon_path,
|
||||
pane.is_zoomed(),
|
||||
Some((tooltip_label, Some(Box::new(ToggleZoom)))),
|
||||
cx,
|
||||
move |pane, cx| pane.toggle_zoom(&Default::default(), cx),
|
||||
None,
|
||||
)
|
||||
})
|
||||
.into_any()
|
||||
}),
|
||||
}
|
||||
|
@ -1112,7 +1121,7 @@ impl Pane {
|
|||
.get(self.active_item_index)
|
||||
.map(|item| item.as_ref());
|
||||
self.toolbar.update(cx, |toolbar, cx| {
|
||||
toolbar.set_active_pane_item(active_item, cx);
|
||||
toolbar.set_active_item(active_item, cx);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -1410,7 +1419,7 @@ impl Pane {
|
|||
pub fn render_tab_bar_button<F: 'static + Fn(&mut Pane, &mut EventContext<Pane>)>(
|
||||
index: usize,
|
||||
icon: &'static str,
|
||||
active: bool,
|
||||
is_active: bool,
|
||||
tooltip: Option<(String, Option<Box<dyn Action>>)>,
|
||||
cx: &mut ViewContext<Pane>,
|
||||
on_click: F,
|
||||
|
@ -1420,7 +1429,7 @@ impl Pane {
|
|||
|
||||
let mut button = MouseEventHandler::<TabBarButton, _>::new(index, cx, |mouse_state, cx| {
|
||||
let theme = &settings::get::<ThemeSettings>(cx).theme.workspace.tab_bar;
|
||||
let style = theme.pane_button.style_for(mouse_state, active);
|
||||
let style = theme.pane_button.in_state(is_active).style_for(mouse_state);
|
||||
Svg::new(icon)
|
||||
.with_color(style.color)
|
||||
.constrained()
|
||||
|
@ -1602,7 +1611,7 @@ impl View for Pane {
|
|||
}
|
||||
|
||||
self.toolbar.update(cx, |toolbar, cx| {
|
||||
toolbar.pane_focus_update(true, cx);
|
||||
toolbar.focus_changed(true, cx);
|
||||
});
|
||||
|
||||
if let Some(active_item) = self.active_item() {
|
||||
|
@ -1631,7 +1640,7 @@ impl View for Pane {
|
|||
fn focus_out(&mut self, _: AnyViewHandle, cx: &mut ViewContext<Self>) {
|
||||
self.has_focus = false;
|
||||
self.toolbar.update(cx, |toolbar, cx| {
|
||||
toolbar.pane_focus_update(false, cx);
|
||||
toolbar.focus_changed(false, cx);
|
||||
});
|
||||
cx.notify();
|
||||
}
|
||||
|
|
|
@ -38,7 +38,7 @@ trait ToolbarItemViewHandle {
|
|||
active_pane_item: Option<&dyn ItemHandle>,
|
||||
cx: &mut WindowContext,
|
||||
) -> ToolbarItemLocation;
|
||||
fn pane_focus_update(&mut self, pane_focused: bool, cx: &mut WindowContext);
|
||||
fn focus_changed(&mut self, pane_focused: bool, cx: &mut WindowContext);
|
||||
fn row_count(&self, cx: &WindowContext) -> usize;
|
||||
}
|
||||
|
||||
|
@ -51,10 +51,10 @@ pub enum ToolbarItemLocation {
|
|||
}
|
||||
|
||||
pub struct Toolbar {
|
||||
active_pane_item: Option<Box<dyn ItemHandle>>,
|
||||
active_item: Option<Box<dyn ItemHandle>>,
|
||||
hidden: bool,
|
||||
can_navigate: bool,
|
||||
pane: WeakViewHandle<Pane>,
|
||||
pane: Option<WeakViewHandle<Pane>>,
|
||||
items: Vec<(Box<dyn ToolbarItemViewHandle>, ToolbarItemLocation)>,
|
||||
}
|
||||
|
||||
|
@ -121,7 +121,7 @@ impl View for Toolbar {
|
|||
let pane = self.pane.clone();
|
||||
let mut enable_go_backward = false;
|
||||
let mut enable_go_forward = false;
|
||||
if let Some(pane) = pane.upgrade(cx) {
|
||||
if let Some(pane) = pane.and_then(|pane| pane.upgrade(cx)) {
|
||||
let pane = pane.read(cx);
|
||||
enable_go_backward = pane.can_navigate_backward();
|
||||
enable_go_forward = pane.can_navigate_forward();
|
||||
|
@ -143,19 +143,17 @@ impl View for Toolbar {
|
|||
enable_go_backward,
|
||||
spacing,
|
||||
{
|
||||
let pane = pane.clone();
|
||||
move |toolbar, cx| {
|
||||
if let Some(workspace) = toolbar
|
||||
.pane
|
||||
.upgrade(cx)
|
||||
.and_then(|pane| pane.read(cx).workspace().upgrade(cx))
|
||||
if let Some(pane) = toolbar.pane.as_ref().and_then(|pane| pane.upgrade(cx))
|
||||
{
|
||||
let pane = pane.clone();
|
||||
cx.window_context().defer(move |cx| {
|
||||
workspace.update(cx, |workspace, cx| {
|
||||
workspace.go_back(pane.clone(), cx).detach_and_log_err(cx);
|
||||
});
|
||||
})
|
||||
if let Some(workspace) = pane.read(cx).workspace().upgrade(cx) {
|
||||
let pane = pane.downgrade();
|
||||
cx.window_context().defer(move |cx| {
|
||||
workspace.update(cx, |workspace, cx| {
|
||||
workspace.go_back(pane, cx).detach_and_log_err(cx);
|
||||
});
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -171,21 +169,17 @@ impl View for Toolbar {
|
|||
enable_go_forward,
|
||||
spacing,
|
||||
{
|
||||
let pane = pane.clone();
|
||||
move |toolbar, cx| {
|
||||
if let Some(workspace) = toolbar
|
||||
.pane
|
||||
.upgrade(cx)
|
||||
.and_then(|pane| pane.read(cx).workspace().upgrade(cx))
|
||||
if let Some(pane) = toolbar.pane.as_ref().and_then(|pane| pane.upgrade(cx))
|
||||
{
|
||||
let pane = pane.clone();
|
||||
cx.window_context().defer(move |cx| {
|
||||
workspace.update(cx, |workspace, cx| {
|
||||
workspace
|
||||
.go_forward(pane.clone(), cx)
|
||||
.detach_and_log_err(cx);
|
||||
});
|
||||
});
|
||||
if let Some(workspace) = pane.read(cx).workspace().upgrade(cx) {
|
||||
let pane = pane.downgrade();
|
||||
cx.window_context().defer(move |cx| {
|
||||
workspace.update(cx, |workspace, cx| {
|
||||
workspace.go_forward(pane, cx).detach_and_log_err(cx);
|
||||
});
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -231,7 +225,7 @@ fn nav_button<A: Action, F: 'static + Fn(&mut Toolbar, &mut ViewContext<Toolbar>
|
|||
) -> AnyElement<Toolbar> {
|
||||
MouseEventHandler::<A, _>::new(0, cx, |state, _| {
|
||||
let style = if enabled {
|
||||
style.style_for(state, false)
|
||||
style.style_for(state)
|
||||
} else {
|
||||
style.disabled_style()
|
||||
};
|
||||
|
@ -269,9 +263,9 @@ fn nav_button<A: Action, F: 'static + Fn(&mut Toolbar, &mut ViewContext<Toolbar>
|
|||
}
|
||||
|
||||
impl Toolbar {
|
||||
pub fn new(pane: WeakViewHandle<Pane>) -> Self {
|
||||
pub fn new(pane: Option<WeakViewHandle<Pane>>) -> Self {
|
||||
Self {
|
||||
active_pane_item: None,
|
||||
active_item: None,
|
||||
pane,
|
||||
items: Default::default(),
|
||||
hidden: false,
|
||||
|
@ -288,7 +282,7 @@ impl Toolbar {
|
|||
where
|
||||
T: 'static + ToolbarItemView,
|
||||
{
|
||||
let location = item.set_active_pane_item(self.active_pane_item.as_deref(), cx);
|
||||
let location = item.set_active_pane_item(self.active_item.as_deref(), cx);
|
||||
cx.subscribe(&item, |this, item, event, cx| {
|
||||
if let Some((_, current_location)) =
|
||||
this.items.iter_mut().find(|(i, _)| i.id() == item.id())
|
||||
|
@ -307,20 +301,16 @@ impl Toolbar {
|
|||
cx.notify();
|
||||
}
|
||||
|
||||
pub fn set_active_pane_item(
|
||||
&mut self,
|
||||
pane_item: Option<&dyn ItemHandle>,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) {
|
||||
self.active_pane_item = pane_item.map(|item| item.boxed_clone());
|
||||
pub fn set_active_item(&mut self, item: Option<&dyn ItemHandle>, cx: &mut ViewContext<Self>) {
|
||||
self.active_item = item.map(|item| item.boxed_clone());
|
||||
self.hidden = self
|
||||
.active_pane_item
|
||||
.active_item
|
||||
.as_ref()
|
||||
.map(|item| !item.show_toolbar(cx))
|
||||
.unwrap_or(false);
|
||||
|
||||
for (toolbar_item, current_location) in self.items.iter_mut() {
|
||||
let new_location = toolbar_item.set_active_pane_item(pane_item, cx);
|
||||
let new_location = toolbar_item.set_active_pane_item(item, cx);
|
||||
if new_location != *current_location {
|
||||
*current_location = new_location;
|
||||
cx.notify();
|
||||
|
@ -328,9 +318,9 @@ impl Toolbar {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn pane_focus_update(&mut self, pane_focused: bool, cx: &mut ViewContext<Self>) {
|
||||
pub fn focus_changed(&mut self, focused: bool, cx: &mut ViewContext<Self>) {
|
||||
for (toolbar_item, _) in self.items.iter_mut() {
|
||||
toolbar_item.pane_focus_update(pane_focused, cx);
|
||||
toolbar_item.focus_changed(focused, cx);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -364,7 +354,7 @@ impl<T: ToolbarItemView> ToolbarItemViewHandle for ViewHandle<T> {
|
|||
})
|
||||
}
|
||||
|
||||
fn pane_focus_update(&mut self, pane_focused: bool, cx: &mut WindowContext) {
|
||||
fn focus_changed(&mut self, pane_focused: bool, cx: &mut WindowContext) {
|
||||
self.update(cx, |this, cx| {
|
||||
this.pane_focus_update(pane_focused, cx);
|
||||
cx.notify();
|
||||
|
|
|
@ -861,7 +861,10 @@ impl Workspace {
|
|||
&self.right_dock
|
||||
}
|
||||
|
||||
pub fn add_panel<T: Panel>(&mut self, panel: ViewHandle<T>, cx: &mut ViewContext<Self>) {
|
||||
pub fn add_panel<T: Panel>(&mut self, panel: ViewHandle<T>, cx: &mut ViewContext<Self>)
|
||||
where
|
||||
T::Event: std::fmt::Debug,
|
||||
{
|
||||
let dock = match panel.position(cx) {
|
||||
DockPosition::Left => &self.left_dock,
|
||||
DockPosition::Bottom => &self.bottom_dock,
|
||||
|
@ -904,10 +907,11 @@ impl Workspace {
|
|||
});
|
||||
} else if T::should_zoom_in_on_event(event) {
|
||||
dock.update(cx, |dock, cx| dock.set_panel_zoomed(&panel, true, cx));
|
||||
if panel.has_focus(cx) {
|
||||
this.zoomed = Some(panel.downgrade().into_any());
|
||||
this.zoomed_position = Some(panel.read(cx).position(cx));
|
||||
if !panel.has_focus(cx) {
|
||||
cx.focus(&panel);
|
||||
}
|
||||
this.zoomed = Some(panel.downgrade().into_any());
|
||||
this.zoomed_position = Some(panel.read(cx).position(cx));
|
||||
} else if T::should_zoom_out_on_event(event) {
|
||||
dock.update(cx, |dock, cx| dock.set_panel_zoomed(&panel, false, cx));
|
||||
if this.zoomed_position == Some(prev_position) {
|
||||
|
@ -1702,6 +1706,11 @@ impl Workspace {
|
|||
cx.notify();
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
pub fn zoomed_view(&self, cx: &AppContext) -> Option<AnyViewHandle> {
|
||||
self.zoomed.and_then(|view| view.upgrade(cx))
|
||||
}
|
||||
|
||||
fn dismiss_zoomed_items_to_reveal(
|
||||
&mut self,
|
||||
dock_to_reveal: Option<DockPosition>,
|
||||
|
|
|
@ -3,7 +3,7 @@ authors = ["Nathan Sobo <nathansobo@gmail.com>"]
|
|||
description = "The fast, collaborative code editor."
|
||||
edition = "2021"
|
||||
name = "zed"
|
||||
version = "0.92.0"
|
||||
version = "0.92.4"
|
||||
publish = false
|
||||
|
||||
[lib]
|
||||
|
@ -62,7 +62,6 @@ text = { path = "../text" }
|
|||
terminal_view = { path = "../terminal_view" }
|
||||
theme = { path = "../theme" }
|
||||
theme_selector = { path = "../theme_selector" }
|
||||
theme_testbench = { path = "../theme_testbench" }
|
||||
util = { path = "../util" }
|
||||
vim = { path = "../vim" }
|
||||
workspace = { path = "../workspace" }
|
||||
|
|
|
@ -1 +1 @@
|
|||
dev
|
||||
stable
|
|
@ -62,16 +62,23 @@ impl LspAdapter for ElixirLspAdapter {
|
|||
&self,
|
||||
delegate: &dyn LspAdapterDelegate,
|
||||
) -> Result<Box<dyn 'static + Send + Any>> {
|
||||
let release =
|
||||
latest_github_release("elixir-lsp/elixir-ls", false, delegate.http_client()).await?;
|
||||
let asset_name = "elixir-ls.zip";
|
||||
let http = delegate.http_client();
|
||||
let release = latest_github_release("elixir-lsp/elixir-ls", false, http).await?;
|
||||
let version_name = release
|
||||
.name
|
||||
.strip_prefix("Release ")
|
||||
.context("Elixir-ls release name does not start with prefix")?
|
||||
.to_owned();
|
||||
|
||||
let asset_name = format!("elixir-ls-{}.zip", &version_name);
|
||||
let asset = release
|
||||
.assets
|
||||
.iter()
|
||||
.find(|asset| asset.name == asset_name)
|
||||
.ok_or_else(|| anyhow!("no asset found matching {:?}", asset_name))?;
|
||||
|
||||
let version = GitHubLspBinaryVersion {
|
||||
name: release.name,
|
||||
name: version_name,
|
||||
url: asset.browser_download_url.clone(),
|
||||
};
|
||||
Ok(Box::new(version) as Box<_>)
|
||||
|
@ -116,7 +123,7 @@ impl LspAdapter for ElixirLspAdapter {
|
|||
.await?
|
||||
.status;
|
||||
if !unzip_status.success() {
|
||||
Err(anyhow!("failed to unzip clangd archive"))?;
|
||||
Err(anyhow!("failed to unzip elixir-ls archive"))?;
|
||||
}
|
||||
|
||||
remove_matching(&container_dir, |entry| entry != version_dir).await;
|
||||
|
|
|
@ -9,7 +9,5 @@
|
|||
(#set! combined)
|
||||
)
|
||||
|
||||
; expressions live within HTML tags, and do not need to be combined
|
||||
; <link href={ Routes.static_path(..) } />
|
||||
((expression (expression_value) @content)
|
||||
(#set! language "elixir"))
|
||||
|
|
|
@ -70,8 +70,6 @@ fn main() {
|
|||
|
||||
init_panic_hook(&app);
|
||||
|
||||
app.background();
|
||||
|
||||
load_embedded_fonts(&app);
|
||||
|
||||
let fs = Arc::new(RealFs);
|
||||
|
@ -154,7 +152,6 @@ fn main() {
|
|||
search::init(cx);
|
||||
vim::init(cx);
|
||||
terminal_view::init(cx);
|
||||
theme_testbench::init(cx);
|
||||
copilot::init(http.clone(), node_runtime, cx);
|
||||
ai::init(cx);
|
||||
|
||||
|
|
|
@ -361,15 +361,15 @@ pub fn initialize_workspace(
|
|||
|
||||
let project_panel = ProjectPanel::load(workspace_handle.clone(), cx.clone());
|
||||
let terminal_panel = TerminalPanel::load(workspace_handle.clone(), cx.clone());
|
||||
let assistant_panel = if *util::channel::RELEASE_CHANNEL == ReleaseChannel::Stable {
|
||||
None
|
||||
} else {
|
||||
Some(AssistantPanel::load(workspace_handle.clone(), cx.clone()).await?)
|
||||
};
|
||||
let (project_panel, terminal_panel) = futures::try_join!(project_panel, terminal_panel)?;
|
||||
let assistant_panel = AssistantPanel::load(workspace_handle.clone(), cx.clone());
|
||||
let (project_panel, terminal_panel, assistant_panel) =
|
||||
futures::try_join!(project_panel, terminal_panel, assistant_panel)?;
|
||||
workspace_handle.update(&mut cx, |workspace, cx| {
|
||||
let project_panel_position = project_panel.position(cx);
|
||||
workspace.add_panel(project_panel, cx);
|
||||
workspace.add_panel(terminal_panel, cx);
|
||||
workspace.add_panel(assistant_panel, cx);
|
||||
|
||||
if !was_deserialized
|
||||
&& workspace
|
||||
.project()
|
||||
|
@ -383,13 +383,7 @@ pub fn initialize_workspace(
|
|||
{
|
||||
workspace.toggle_dock(project_panel_position, cx);
|
||||
}
|
||||
|
||||
cx.focus_self();
|
||||
|
||||
workspace.add_panel(terminal_panel, cx);
|
||||
if let Some(assistant_panel) = assistant_panel {
|
||||
workspace.add_panel(assistant_panel, cx);
|
||||
}
|
||||
})?;
|
||||
Ok(())
|
||||
})
|
||||
|
|
1
styles/.gitignore
vendored
1
styles/.gitignore
vendored
|
@ -1 +1,2 @@
|
|||
node_modules/
|
||||
coverage/
|
||||
|
|
20
styles/.zed/settings.json
Normal file
20
styles/.zed/settings.json
Normal file
|
@ -0,0 +1,20 @@
|
|||
// Folder-specific settings
|
||||
//
|
||||
// For a full list of overridable settings, and general information on folder-specific settings,
|
||||
// see the documentation: https://docs.zed.dev/configuration/configuring-zed#folder-specific-settings
|
||||
{
|
||||
"languages": {
|
||||
"TypeScript": {
|
||||
"tab_size": 4
|
||||
},
|
||||
"TSX": {
|
||||
"tab_size": 4
|
||||
},
|
||||
"JavaScript": {
|
||||
"tab_size": 4
|
||||
},
|
||||
"JSON": {
|
||||
"tab_size": 4
|
||||
}
|
||||
}
|
||||
}
|
2334
styles/package-lock.json
generated
2334
styles/package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
@ -6,7 +6,8 @@
|
|||
"scripts": {
|
||||
"build": "ts-node ./src/buildThemes.ts",
|
||||
"build-licenses": "ts-node ./src/buildLicenses.ts",
|
||||
"build-tokens": "ts-node ./src/buildTokens.ts"
|
||||
"build-tokens": "ts-node ./src/buildTokens.ts",
|
||||
"test": "vitest"
|
||||
},
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
|
@ -20,12 +21,18 @@
|
|||
"chroma-js": "^2.4.2",
|
||||
"deepmerge": "^4.3.0",
|
||||
"toml": "^3.0.0",
|
||||
"ts-node": "^10.9.1"
|
||||
"ts-deepmerge": "^6.0.3",
|
||||
"ts-node": "^10.9.1",
|
||||
"utility-types": "^3.10.0",
|
||||
"vitest": "^0.32.0"
|
||||
},
|
||||
"prettier": {
|
||||
"semi": false,
|
||||
"printWidth": 80,
|
||||
"htmlWhitespaceSensitivity": "strict",
|
||||
"tabWidth": 4
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vitest/coverage-v8": "^0.32.0"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
import * as fs from "fs";
|
||||
import * as path from "path";
|
||||
import { ColorScheme, createColorScheme } from "./common";
|
||||
import { themes } from "./themes";
|
||||
import { slugify } from "./utils/slugify";
|
||||
import { colorSchemeTokens } from "./theme/tokens/colorScheme";
|
||||
import * as fs from "fs"
|
||||
import * as path from "path"
|
||||
import { ColorScheme, createColorScheme } from "./common"
|
||||
import { themes } from "./themes"
|
||||
import { slugify } from "./utils/slugify"
|
||||
import { colorSchemeTokens } from "./theme/tokens/colorScheme"
|
||||
|
||||
const TOKENS_DIRECTORY = path.join(__dirname, "..", "target", "tokens");
|
||||
const TOKENS_FILE = path.join(TOKENS_DIRECTORY, "$themes.json");
|
||||
const METADATA_FILE = path.join(TOKENS_DIRECTORY, "$metadata.json");
|
||||
const TOKENS_DIRECTORY = path.join(__dirname, "..", "target", "tokens")
|
||||
const TOKENS_FILE = path.join(TOKENS_DIRECTORY, "$themes.json")
|
||||
const METADATA_FILE = path.join(TOKENS_DIRECTORY, "$metadata.json")
|
||||
|
||||
function clearTokens(tokensDirectory: string) {
|
||||
if (!fs.existsSync(tokensDirectory)) {
|
||||
|
@ -22,64 +22,66 @@ function clearTokens(tokensDirectory: string) {
|
|||
}
|
||||
|
||||
type TokenSet = {
|
||||
id: string;
|
||||
name: string;
|
||||
selectedTokenSets: { [key: string]: "enabled" };
|
||||
};
|
||||
id: string
|
||||
name: string
|
||||
selectedTokenSets: { [key: string]: "enabled" }
|
||||
}
|
||||
|
||||
function buildTokenSetOrder(colorSchemes: ColorScheme[]): { tokenSetOrder: string[] } {
|
||||
const tokenSetOrder: string[] = colorSchemes.map(
|
||||
(scheme) => scheme.name.toLowerCase().replace(/\s+/g, "_")
|
||||
);
|
||||
return { tokenSetOrder };
|
||||
function buildTokenSetOrder(colorSchemes: ColorScheme[]): {
|
||||
tokenSetOrder: string[]
|
||||
} {
|
||||
const tokenSetOrder: string[] = colorSchemes.map((scheme) =>
|
||||
scheme.name.toLowerCase().replace(/\s+/g, "_")
|
||||
)
|
||||
return { tokenSetOrder }
|
||||
}
|
||||
|
||||
function buildThemesIndex(colorSchemes: ColorScheme[]): TokenSet[] {
|
||||
const themesIndex: TokenSet[] = colorSchemes.map((scheme, index) => {
|
||||
const id = `${scheme.isLight ? "light" : "dark"}_${scheme.name
|
||||
.toLowerCase()
|
||||
.replace(/\s+/g, "_")}_${index}`;
|
||||
const selectedTokenSets: { [key: string]: "enabled" } = {};
|
||||
const tokenSet = scheme.name.toLowerCase().replace(/\s+/g, "_");
|
||||
selectedTokenSets[tokenSet] = "enabled";
|
||||
.replace(/\s+/g, "_")}_${index}`
|
||||
const selectedTokenSets: { [key: string]: "enabled" } = {}
|
||||
const tokenSet = scheme.name.toLowerCase().replace(/\s+/g, "_")
|
||||
selectedTokenSets[tokenSet] = "enabled"
|
||||
|
||||
return {
|
||||
id,
|
||||
name: `${scheme.name} - ${scheme.isLight ? "Light" : "Dark"}`,
|
||||
selectedTokenSets,
|
||||
};
|
||||
});
|
||||
}
|
||||
})
|
||||
|
||||
return themesIndex;
|
||||
return themesIndex
|
||||
}
|
||||
|
||||
function writeTokens(colorSchemes: ColorScheme[], tokensDirectory: string) {
|
||||
clearTokens(tokensDirectory);
|
||||
clearTokens(tokensDirectory)
|
||||
|
||||
for (const colorScheme of colorSchemes) {
|
||||
const fileName = slugify(colorScheme.name) + ".json";
|
||||
const tokens = colorSchemeTokens(colorScheme);
|
||||
const tokensJSON = JSON.stringify(tokens, null, 2);
|
||||
const outPath = path.join(tokensDirectory, fileName);
|
||||
fs.writeFileSync(outPath, tokensJSON, { mode: 0o644 });
|
||||
console.log(`- ${outPath} created`);
|
||||
const fileName = slugify(colorScheme.name) + ".json"
|
||||
const tokens = colorSchemeTokens(colorScheme)
|
||||
const tokensJSON = JSON.stringify(tokens, null, 2)
|
||||
const outPath = path.join(tokensDirectory, fileName)
|
||||
fs.writeFileSync(outPath, tokensJSON, { mode: 0o644 })
|
||||
console.log(`- ${outPath} created`)
|
||||
}
|
||||
|
||||
const themeIndexData = buildThemesIndex(colorSchemes);
|
||||
const themeIndexData = buildThemesIndex(colorSchemes)
|
||||
|
||||
const themesJSON = JSON.stringify(themeIndexData, null, 2);
|
||||
fs.writeFileSync(TOKENS_FILE, themesJSON, { mode: 0o644 });
|
||||
console.log(`- ${TOKENS_FILE} created`);
|
||||
const themesJSON = JSON.stringify(themeIndexData, null, 2)
|
||||
fs.writeFileSync(TOKENS_FILE, themesJSON, { mode: 0o644 })
|
||||
console.log(`- ${TOKENS_FILE} created`)
|
||||
|
||||
const tokenSetOrderData = buildTokenSetOrder(colorSchemes);
|
||||
const tokenSetOrderData = buildTokenSetOrder(colorSchemes)
|
||||
|
||||
const metadataJSON = JSON.stringify(tokenSetOrderData, null, 2);
|
||||
fs.writeFileSync(METADATA_FILE, metadataJSON, { mode: 0o644 });
|
||||
console.log(`- ${METADATA_FILE} created`);
|
||||
const metadataJSON = JSON.stringify(tokenSetOrderData, null, 2)
|
||||
fs.writeFileSync(METADATA_FILE, metadataJSON, { mode: 0o644 })
|
||||
console.log(`- ${METADATA_FILE} created`)
|
||||
}
|
||||
|
||||
const colorSchemes: ColorScheme[] = themes.map((theme) =>
|
||||
createColorScheme(theme)
|
||||
);
|
||||
)
|
||||
|
||||
writeTokens(colorSchemes, TOKENS_DIRECTORY);
|
||||
writeTokens(colorSchemes, TOKENS_DIRECTORY)
|
||||
|
|
86
styles/src/component/icon_button.ts
Normal file
86
styles/src/component/icon_button.ts
Normal file
|
@ -0,0 +1,86 @@
|
|||
import { ColorScheme } from "../common"
|
||||
import { interactive, toggleable } from "../element"
|
||||
import { background, foreground } from "../styleTree/components"
|
||||
|
||||
export type Margin = {
|
||||
top: number
|
||||
bottom: number
|
||||
left: number
|
||||
right: number
|
||||
}
|
||||
|
||||
interface IconButtonOptions {
|
||||
layer?:
|
||||
| ColorScheme["lowest"]
|
||||
| ColorScheme["middle"]
|
||||
| ColorScheme["highest"]
|
||||
color?: keyof ColorScheme["lowest"]
|
||||
margin?: Partial<Margin>
|
||||
}
|
||||
|
||||
type ToggleableIconButtonOptions = IconButtonOptions & {
|
||||
active_color?: keyof ColorScheme["lowest"]
|
||||
}
|
||||
|
||||
export function icon_button(
|
||||
theme: ColorScheme,
|
||||
{ color, margin, layer }: IconButtonOptions
|
||||
) {
|
||||
if (!color) color = "base"
|
||||
|
||||
const m = {
|
||||
top: margin?.top ?? 0,
|
||||
bottom: margin?.bottom ?? 0,
|
||||
left: margin?.left ?? 0,
|
||||
right: margin?.right ?? 0,
|
||||
}
|
||||
|
||||
return interactive({
|
||||
base: {
|
||||
corner_radius: 6,
|
||||
padding: {
|
||||
top: 2,
|
||||
bottom: 2,
|
||||
left: 4,
|
||||
right: 4,
|
||||
},
|
||||
margin: m,
|
||||
icon_width: 14,
|
||||
icon_height: 14,
|
||||
button_width: 20,
|
||||
button_height: 16,
|
||||
},
|
||||
state: {
|
||||
default: {
|
||||
background: background(layer ?? theme.lowest, color),
|
||||
color: foreground(layer ?? theme.lowest, color),
|
||||
},
|
||||
hovered: {
|
||||
background: background(layer ?? theme.lowest, color, "hovered"),
|
||||
color: foreground(layer ?? theme.lowest, color, "hovered"),
|
||||
},
|
||||
clicked: {
|
||||
background: background(layer ?? theme.lowest, color, "pressed"),
|
||||
color: foreground(layer ?? theme.lowest, color, "pressed"),
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export function toggleable_icon_button(
|
||||
theme: ColorScheme,
|
||||
{ color, active_color, margin }: ToggleableIconButtonOptions
|
||||
) {
|
||||
if (!color) color = "base"
|
||||
|
||||
return toggleable({
|
||||
state: {
|
||||
inactive: icon_button(theme, { color, margin }),
|
||||
active: icon_button(theme, {
|
||||
color: active_color ? active_color : color,
|
||||
margin,
|
||||
layer: theme.middle,
|
||||
}),
|
||||
},
|
||||
})
|
||||
}
|
90
styles/src/component/text_button.ts
Normal file
90
styles/src/component/text_button.ts
Normal file
|
@ -0,0 +1,90 @@
|
|||
import { ColorScheme } from "../common"
|
||||
import { interactive, toggleable } from "../element"
|
||||
import {
|
||||
TextProperties,
|
||||
background,
|
||||
foreground,
|
||||
text,
|
||||
} from "../styleTree/components"
|
||||
import { Margin } from "./icon_button"
|
||||
|
||||
interface TextButtonOptions {
|
||||
layer?:
|
||||
| ColorScheme["lowest"]
|
||||
| ColorScheme["middle"]
|
||||
| ColorScheme["highest"]
|
||||
color?: keyof ColorScheme["lowest"]
|
||||
margin?: Partial<Margin>
|
||||
text_properties?: TextProperties
|
||||
}
|
||||
|
||||
type ToggleableTextButtonOptions = TextButtonOptions & {
|
||||
active_color?: keyof ColorScheme["lowest"]
|
||||
}
|
||||
|
||||
export function text_button(
|
||||
theme: ColorScheme,
|
||||
{ color, layer, margin, text_properties }: TextButtonOptions
|
||||
) {
|
||||
if (!color) color = "base"
|
||||
|
||||
const text_options: TextProperties = {
|
||||
size: "xs",
|
||||
weight: "normal",
|
||||
...text_properties,
|
||||
}
|
||||
|
||||
const m = {
|
||||
top: margin?.top ?? 0,
|
||||
bottom: margin?.bottom ?? 0,
|
||||
left: margin?.left ?? 0,
|
||||
right: margin?.right ?? 0,
|
||||
}
|
||||
|
||||
return interactive({
|
||||
base: {
|
||||
corner_radius: 6,
|
||||
padding: {
|
||||
top: 1,
|
||||
bottom: 1,
|
||||
left: 6,
|
||||
right: 6,
|
||||
},
|
||||
margin: m,
|
||||
button_height: 22,
|
||||
...text(layer ?? theme.lowest, "sans", color, text_options),
|
||||
},
|
||||
state: {
|
||||
default: {
|
||||
background: background(layer ?? theme.lowest, color),
|
||||
color: foreground(layer ?? theme.lowest, color),
|
||||
},
|
||||
hovered: {
|
||||
background: background(layer ?? theme.lowest, color, "hovered"),
|
||||
color: foreground(layer ?? theme.lowest, color, "hovered"),
|
||||
},
|
||||
clicked: {
|
||||
background: background(layer ?? theme.lowest, color, "pressed"),
|
||||
color: foreground(layer ?? theme.lowest, color, "pressed"),
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export function toggleable_text_button(
|
||||
theme: ColorScheme,
|
||||
{ color, active_color, margin }: ToggleableTextButtonOptions
|
||||
) {
|
||||
if (!color) color = "base"
|
||||
|
||||
return toggleable({
|
||||
state: {
|
||||
inactive: text_button(theme, { color, margin }),
|
||||
active: text_button(theme, {
|
||||
color: active_color ? active_color : color,
|
||||
margin,
|
||||
layer: theme.middle,
|
||||
}),
|
||||
},
|
||||
})
|
||||
}
|
4
styles/src/element/index.ts
Normal file
4
styles/src/element/index.ts
Normal file
|
@ -0,0 +1,4 @@
|
|||
import { interactive } from "./interactive"
|
||||
import { toggleable } from "./toggle"
|
||||
|
||||
export { interactive, toggleable }
|
56
styles/src/element/interactive.test.ts
Normal file
56
styles/src/element/interactive.test.ts
Normal file
|
@ -0,0 +1,56 @@
|
|||
import {
|
||||
NOT_ENOUGH_STATES_ERROR,
|
||||
NO_DEFAULT_OR_BASE_ERROR,
|
||||
interactive,
|
||||
} from "./interactive"
|
||||
import { describe, it, expect } from "vitest"
|
||||
|
||||
describe("interactive", () => {
|
||||
it("creates an Interactive<Element> with base properties and states", () => {
|
||||
const result = interactive({
|
||||
base: { fontSize: 10, color: "#FFFFFF" },
|
||||
state: {
|
||||
hovered: { color: "#EEEEEE" },
|
||||
clicked: { color: "#CCCCCC" },
|
||||
},
|
||||
})
|
||||
|
||||
expect(result).toEqual({
|
||||
default: { color: "#FFFFFF", fontSize: 10 },
|
||||
hovered: { color: "#EEEEEE", fontSize: 10 },
|
||||
clicked: { color: "#CCCCCC", fontSize: 10 },
|
||||
})
|
||||
})
|
||||
|
||||
it("creates an Interactive<Element> with no base properties", () => {
|
||||
const result = interactive({
|
||||
state: {
|
||||
default: { color: "#FFFFFF", fontSize: 10 },
|
||||
hovered: { color: "#EEEEEE" },
|
||||
clicked: { color: "#CCCCCC" },
|
||||
},
|
||||
})
|
||||
|
||||
expect(result).toEqual({
|
||||
default: { color: "#FFFFFF", fontSize: 10 },
|
||||
hovered: { color: "#EEEEEE", fontSize: 10 },
|
||||
clicked: { color: "#CCCCCC", fontSize: 10 },
|
||||
})
|
||||
})
|
||||
|
||||
it("throws error when both default and base are missing", () => {
|
||||
const state = {
|
||||
hovered: { color: "blue" },
|
||||
}
|
||||
|
||||
expect(() => interactive({ state })).toThrow(NO_DEFAULT_OR_BASE_ERROR)
|
||||
})
|
||||
|
||||
it("throws error when no other state besides default is present", () => {
|
||||
const state = {
|
||||
default: { fontSize: 10 },
|
||||
}
|
||||
|
||||
expect(() => interactive({ state })).toThrow(NOT_ENOUGH_STATES_ERROR)
|
||||
})
|
||||
})
|
97
styles/src/element/interactive.ts
Normal file
97
styles/src/element/interactive.ts
Normal file
|
@ -0,0 +1,97 @@
|
|||
import merge from "ts-deepmerge"
|
||||
import { DeepPartial } from "utility-types"
|
||||
|
||||
export type InteractiveState =
|
||||
| "default"
|
||||
| "hovered"
|
||||
| "clicked"
|
||||
| "selected"
|
||||
| "disabled"
|
||||
|
||||
type Interactive<T> = {
|
||||
default: T
|
||||
hovered?: T
|
||||
clicked?: T
|
||||
selected?: T
|
||||
disabled?: T
|
||||
}
|
||||
|
||||
export const NO_DEFAULT_OR_BASE_ERROR =
|
||||
"An interactive object must have a default state, or a base property."
|
||||
export const NOT_ENOUGH_STATES_ERROR =
|
||||
"An interactive object must have a default and at least one other state."
|
||||
|
||||
interface InteractiveProps<T> {
|
||||
base?: T
|
||||
state: Partial<Record<InteractiveState, DeepPartial<T>>>
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function for creating Interactive<T> objects that works with Toggle<T>-like behavior.
|
||||
* It takes a default object to be used as the value for `default` field and fills out other fields
|
||||
* with fields from either `base` or from the `state` object which contains values for specific states.
|
||||
* Notably, it does not touch `hover`, `clicked`, `selected` and `disabled` states if there are no modifications for them.
|
||||
*
|
||||
* @param defaultObj Object to be used as the value for the `default` field.
|
||||
* @param base Optional object containing base fields to be included in the resulting object.
|
||||
* @param state Object containing optional modified fields to be included in the resulting object for each state.
|
||||
* @returns Interactive<T> object with fields from `base` and `state`.
|
||||
*/
|
||||
export function interactive<T extends Object>({
|
||||
base,
|
||||
state,
|
||||
}: InteractiveProps<T>): Interactive<T> {
|
||||
if (!base && !state.default) throw new Error(NO_DEFAULT_OR_BASE_ERROR)
|
||||
|
||||
let defaultState: T
|
||||
|
||||
if (state.default && base) {
|
||||
defaultState = merge(base, state.default) as T
|
||||
} else {
|
||||
defaultState = base ? base : (state.default as T)
|
||||
}
|
||||
|
||||
let interactiveObj: Interactive<T> = {
|
||||
default: defaultState,
|
||||
}
|
||||
|
||||
let stateCount = 0
|
||||
|
||||
if (state.hovered !== undefined) {
|
||||
interactiveObj.hovered = merge(
|
||||
interactiveObj.default,
|
||||
state.hovered
|
||||
) as T
|
||||
stateCount++
|
||||
}
|
||||
|
||||
if (state.clicked !== undefined) {
|
||||
interactiveObj.clicked = merge(
|
||||
interactiveObj.default,
|
||||
state.clicked
|
||||
) as T
|
||||
stateCount++
|
||||
}
|
||||
|
||||
if (state.selected !== undefined) {
|
||||
interactiveObj.selected = merge(
|
||||
interactiveObj.default,
|
||||
state.selected
|
||||
) as T
|
||||
stateCount++
|
||||
}
|
||||
|
||||
if (state.disabled !== undefined) {
|
||||
interactiveObj.disabled = merge(
|
||||
interactiveObj.default,
|
||||
state.disabled
|
||||
) as T
|
||||
stateCount++
|
||||
}
|
||||
|
||||
if (stateCount < 1) {
|
||||
throw new Error(NOT_ENOUGH_STATES_ERROR)
|
||||
}
|
||||
|
||||
return interactiveObj
|
||||
}
|
52
styles/src/element/toggle.test.ts
Normal file
52
styles/src/element/toggle.test.ts
Normal file
|
@ -0,0 +1,52 @@
|
|||
import {
|
||||
NO_ACTIVE_ERROR,
|
||||
NO_INACTIVE_OR_BASE_ERROR,
|
||||
toggleable,
|
||||
} from "./toggle"
|
||||
import { describe, it, expect } from "vitest"
|
||||
|
||||
describe("toggleable", () => {
|
||||
it("creates a Toggleable<Element> with base properties and states", () => {
|
||||
const result = toggleable({
|
||||
base: { background: "#000000", color: "#CCCCCC" },
|
||||
state: {
|
||||
active: { color: "#FFFFFF" },
|
||||
},
|
||||
})
|
||||
|
||||
expect(result).toEqual({
|
||||
inactive: { background: "#000000", color: "#CCCCCC" },
|
||||
active: { background: "#000000", color: "#FFFFFF" },
|
||||
})
|
||||
})
|
||||
|
||||
it("creates a Toggleable<Element> with no base properties", () => {
|
||||
const result = toggleable({
|
||||
state: {
|
||||
inactive: { background: "#000000", color: "#CCCCCC" },
|
||||
active: { background: "#000000", color: "#FFFFFF" },
|
||||
},
|
||||
})
|
||||
|
||||
expect(result).toEqual({
|
||||
inactive: { background: "#000000", color: "#CCCCCC" },
|
||||
active: { background: "#000000", color: "#FFFFFF" },
|
||||
})
|
||||
})
|
||||
|
||||
it("throws error when both inactive and base are missing", () => {
|
||||
const state = {
|
||||
active: { background: "#000000", color: "#FFFFFF" },
|
||||
}
|
||||
|
||||
expect(() => toggleable({ state })).toThrow(NO_INACTIVE_OR_BASE_ERROR)
|
||||
})
|
||||
|
||||
it("throws error when no active state is present", () => {
|
||||
const state = {
|
||||
inactive: { background: "#000000", color: "#CCCCCC" },
|
||||
}
|
||||
|
||||
expect(() => toggleable({ state })).toThrow(NO_ACTIVE_ERROR)
|
||||
})
|
||||
})
|
47
styles/src/element/toggle.ts
Normal file
47
styles/src/element/toggle.ts
Normal file
|
@ -0,0 +1,47 @@
|
|||
import merge from "ts-deepmerge"
|
||||
import { DeepPartial } from "utility-types"
|
||||
|
||||
type ToggleState = "inactive" | "active"
|
||||
|
||||
type Toggleable<T> = Record<ToggleState, T>
|
||||
|
||||
export const NO_INACTIVE_OR_BASE_ERROR =
|
||||
"A toggleable object must have an inactive state, or a base property."
|
||||
export const NO_ACTIVE_ERROR = "A toggleable object must have an active state."
|
||||
|
||||
interface ToggleableProps<T> {
|
||||
base?: T
|
||||
state: Partial<Record<ToggleState, DeepPartial<T>>>
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function for creating Toggleable objects.
|
||||
* @template T The type of the object being toggled.
|
||||
* @param props Object containing the base (inactive) state and state modifications to create the active state.
|
||||
* @returns A Toggleable object containing both the inactive and active states.
|
||||
* @example
|
||||
* ```
|
||||
* toggleable({
|
||||
* base: { background: "#000000", text: "#CCCCCC" },
|
||||
* state: { active: { text: "#CCCCCC" } },
|
||||
* })
|
||||
* ```
|
||||
*/
|
||||
export function toggleable<T extends object>(
|
||||
props: ToggleableProps<T>
|
||||
): Toggleable<T> {
|
||||
const { base, state } = props
|
||||
|
||||
if (!base && !state.inactive) throw new Error(NO_INACTIVE_OR_BASE_ERROR)
|
||||
if (!state.active) throw new Error(NO_ACTIVE_ERROR)
|
||||
|
||||
const inactiveState = base
|
||||
? ((state.inactive ? merge(base, state.inactive) : base) as T)
|
||||
: (state.inactive as T)
|
||||
|
||||
const toggleObj: Toggleable<T> = {
|
||||
inactive: inactiveState,
|
||||
active: merge(base ?? {}, state.active) as T,
|
||||
}
|
||||
return toggleObj
|
||||
}
|
|
@ -1,4 +1,3 @@
|
|||
import { text } from "./components"
|
||||
import contactFinder from "./contactFinder"
|
||||
import contactsPopover from "./contactsPopover"
|
||||
import commandPalette from "./commandPalette"
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import { ColorScheme } from "../theme/colorScheme"
|
||||
import { text, border, background, foreground } from "./components"
|
||||
import editor from "./editor"
|
||||
import { interactive } from "../element"
|
||||
|
||||
export default function assistant(colorScheme: ColorScheme) {
|
||||
const layer = colorScheme.highest
|
||||
|
@ -9,50 +10,245 @@ export default function assistant(colorScheme: ColorScheme) {
|
|||
background: editor(colorScheme).background,
|
||||
padding: { left: 12 },
|
||||
},
|
||||
header: {
|
||||
border: border(layer, "default", { bottom: true, top: true }),
|
||||
messageHeader: {
|
||||
margin: { bottom: 6, top: 6 },
|
||||
background: editor(colorScheme).background,
|
||||
},
|
||||
hamburgerButton: interactive({
|
||||
base: {
|
||||
icon: {
|
||||
color: foreground(layer, "variant"),
|
||||
asset: "icons/hamburger_15.svg",
|
||||
dimensions: {
|
||||
width: 15,
|
||||
height: 15,
|
||||
},
|
||||
},
|
||||
container: {
|
||||
padding: { left: 12, right: 8.5 },
|
||||
},
|
||||
},
|
||||
state: {
|
||||
hovered: {
|
||||
icon: {
|
||||
color: foreground(layer, "hovered"),
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
splitButton: interactive({
|
||||
base: {
|
||||
icon: {
|
||||
color: foreground(layer, "variant"),
|
||||
asset: "icons/split_message_15.svg",
|
||||
dimensions: {
|
||||
width: 15,
|
||||
height: 15,
|
||||
},
|
||||
},
|
||||
container: {
|
||||
padding: { left: 8.5, right: 8.5 },
|
||||
},
|
||||
},
|
||||
state: {
|
||||
hovered: {
|
||||
icon: {
|
||||
color: foreground(layer, "hovered"),
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
quoteButton: interactive({
|
||||
base: {
|
||||
icon: {
|
||||
color: foreground(layer, "variant"),
|
||||
asset: "icons/quote_15.svg",
|
||||
dimensions: {
|
||||
width: 15,
|
||||
height: 15,
|
||||
},
|
||||
},
|
||||
container: {
|
||||
padding: { left: 8.5, right: 8.5 },
|
||||
},
|
||||
},
|
||||
state: {
|
||||
hovered: {
|
||||
icon: {
|
||||
color: foreground(layer, "hovered"),
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
assistButton: interactive({
|
||||
base: {
|
||||
icon: {
|
||||
color: foreground(layer, "variant"),
|
||||
asset: "icons/assist_15.svg",
|
||||
dimensions: {
|
||||
width: 15,
|
||||
height: 15,
|
||||
},
|
||||
},
|
||||
container: {
|
||||
padding: { left: 8.5, right: 8.5 },
|
||||
},
|
||||
},
|
||||
state: {
|
||||
hovered: {
|
||||
icon: {
|
||||
color: foreground(layer, "hovered"),
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
zoomInButton: interactive({
|
||||
base: {
|
||||
icon: {
|
||||
color: foreground(layer, "variant"),
|
||||
asset: "icons/maximize_8.svg",
|
||||
dimensions: {
|
||||
width: 12,
|
||||
height: 12,
|
||||
},
|
||||
},
|
||||
container: {
|
||||
padding: { left: 10, right: 10 },
|
||||
},
|
||||
},
|
||||
state: {
|
||||
hovered: {
|
||||
icon: {
|
||||
color: foreground(layer, "hovered"),
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
zoomOutButton: interactive({
|
||||
base: {
|
||||
icon: {
|
||||
color: foreground(layer, "variant"),
|
||||
asset: "icons/minimize_8.svg",
|
||||
dimensions: {
|
||||
width: 12,
|
||||
height: 12,
|
||||
},
|
||||
},
|
||||
container: {
|
||||
padding: { left: 10, right: 10 },
|
||||
},
|
||||
},
|
||||
state: {
|
||||
hovered: {
|
||||
icon: {
|
||||
color: foreground(layer, "hovered"),
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
plusButton: interactive({
|
||||
base: {
|
||||
icon: {
|
||||
color: foreground(layer, "variant"),
|
||||
asset: "icons/plus_12.svg",
|
||||
dimensions: {
|
||||
width: 12,
|
||||
height: 12,
|
||||
},
|
||||
},
|
||||
container: {
|
||||
padding: { left: 10, right: 10 },
|
||||
},
|
||||
},
|
||||
state: {
|
||||
hovered: {
|
||||
icon: {
|
||||
color: foreground(layer, "hovered"),
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
title: {
|
||||
...text(layer, "sans", "default", { size: "sm" }),
|
||||
},
|
||||
savedConversation: {
|
||||
container: interactive({
|
||||
base: {
|
||||
background: background(layer, "on"),
|
||||
padding: { top: 4, bottom: 4 },
|
||||
},
|
||||
state: {
|
||||
hovered: {
|
||||
background: background(layer, "on", "hovered"),
|
||||
},
|
||||
},
|
||||
}),
|
||||
savedAt: {
|
||||
margin: { left: 8 },
|
||||
...text(layer, "sans", "default", { size: "xs" }),
|
||||
},
|
||||
title: {
|
||||
margin: { left: 16 },
|
||||
...text(layer, "sans", "default", {
|
||||
size: "sm",
|
||||
weight: "bold",
|
||||
}),
|
||||
},
|
||||
},
|
||||
userSender: {
|
||||
...text(layer, "sans", "default", { size: "sm", weight: "bold" }),
|
||||
default: {
|
||||
...text(layer, "sans", "default", {
|
||||
size: "sm",
|
||||
weight: "bold",
|
||||
}),
|
||||
},
|
||||
},
|
||||
assistantSender: {
|
||||
...text(layer, "sans", "accent", { size: "sm", weight: "bold" }),
|
||||
default: {
|
||||
...text(layer, "sans", "accent", {
|
||||
size: "sm",
|
||||
weight: "bold",
|
||||
}),
|
||||
},
|
||||
},
|
||||
systemSender: {
|
||||
...text(layer, "sans", "variant", { size: "sm", weight: "bold" }),
|
||||
default: {
|
||||
...text(layer, "sans", "variant", {
|
||||
size: "sm",
|
||||
weight: "bold",
|
||||
}),
|
||||
},
|
||||
},
|
||||
sentAt: {
|
||||
margin: { top: 2, left: 8 },
|
||||
...text(layer, "sans", "default", { size: "2xs" }),
|
||||
},
|
||||
modelInfoContainer: {
|
||||
margin: { right: 16, top: 4 },
|
||||
},
|
||||
model: {
|
||||
background: background(layer, "on"),
|
||||
border: border(layer, "on", { overlay: true }),
|
||||
padding: 4,
|
||||
cornerRadius: 4,
|
||||
...text(layer, "sans", "default", { size: "xs" }),
|
||||
hover: {
|
||||
background: background(layer, "on", "hovered"),
|
||||
model: interactive({
|
||||
base: {
|
||||
background: background(layer, "on"),
|
||||
margin: { left: 12, right: 12, top: 12 },
|
||||
padding: 4,
|
||||
cornerRadius: 4,
|
||||
...text(layer, "sans", "default", { size: "xs" }),
|
||||
},
|
||||
},
|
||||
state: {
|
||||
hovered: {
|
||||
background: background(layer, "on", "hovered"),
|
||||
border: border(layer, "on", { overlay: true }),
|
||||
},
|
||||
},
|
||||
}),
|
||||
remainingTokens: {
|
||||
background: background(layer, "on"),
|
||||
border: border(layer, "on", { overlay: true }),
|
||||
margin: { top: 12, right: 24 },
|
||||
padding: 4,
|
||||
margin: { left: 4 },
|
||||
cornerRadius: 4,
|
||||
...text(layer, "sans", "positive", { size: "xs" }),
|
||||
},
|
||||
noRemainingTokens: {
|
||||
background: background(layer, "on"),
|
||||
border: border(layer, "on", { overlay: true }),
|
||||
margin: { top: 12, right: 24 },
|
||||
padding: 4,
|
||||
margin: { left: 4 },
|
||||
cornerRadius: 4,
|
||||
...text(layer, "sans", "negative", { size: "xs" }),
|
||||
},
|
||||
|
|
|
@ -1,12 +1,13 @@
|
|||
import { ColorScheme } from "../theme/colorScheme"
|
||||
import { withOpacity } from "../theme/color"
|
||||
import { text, background } from "./components"
|
||||
import { toggleable } from "../element"
|
||||
|
||||
export default function commandPalette(colorScheme: ColorScheme) {
|
||||
let layer = colorScheme.highest
|
||||
return {
|
||||
keystrokeSpacing: 8,
|
||||
key: {
|
||||
|
||||
const key = toggleable({
|
||||
base: {
|
||||
text: text(layer, "mono", "variant", "default", { size: "xs" }),
|
||||
cornerRadius: 2,
|
||||
background: background(layer, "on"),
|
||||
|
@ -21,10 +22,21 @@ export default function commandPalette(colorScheme: ColorScheme) {
|
|||
bottom: 1,
|
||||
left: 2,
|
||||
},
|
||||
},
|
||||
state: {
|
||||
active: {
|
||||
text: text(layer, "mono", "on", "default", { size: "xs" }),
|
||||
background: withOpacity(background(layer, "on"), 0.2),
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
return {
|
||||
keystrokeSpacing: 8,
|
||||
// TODO: This should be a Toggle<ContainedText> on the rust side so we don't have to do this
|
||||
key: {
|
||||
inactive: { ...key.inactive },
|
||||
active: key.active,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
|
@ -85,7 +85,15 @@ export function foreground(
|
|||
return getStyle(layer, styleSetOrStyles, style).foreground
|
||||
}
|
||||
|
||||
interface Text {
|
||||
interface Text extends Object {
|
||||
family: keyof typeof fontFamilies
|
||||
color: string
|
||||
size: number
|
||||
weight?: FontWeight
|
||||
underline?: boolean
|
||||
}
|
||||
|
||||
export interface TextStyle extends Object {
|
||||
family: keyof typeof fontFamilies
|
||||
color: string
|
||||
size: number
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { ColorScheme } from "../theme/colorScheme"
|
||||
import { background, border, borderColor, foreground, text } from "./components"
|
||||
|
||||
import { interactive, toggleable } from "../element"
|
||||
export default function contactsPanel(colorScheme: ColorScheme) {
|
||||
const nameMargin = 8
|
||||
const sidePadding = 12
|
||||
|
@ -71,47 +71,85 @@ export default function contactsPanel(colorScheme: ColorScheme) {
|
|||
},
|
||||
rowHeight: 28,
|
||||
sectionIconSize: 8,
|
||||
headerRow: {
|
||||
...text(layer, "mono", { size: "sm" }),
|
||||
margin: { top: 14 },
|
||||
padding: {
|
||||
left: sidePadding,
|
||||
right: sidePadding,
|
||||
headerRow: toggleable({
|
||||
base: interactive({
|
||||
base: {
|
||||
...text(layer, "mono", { size: "sm" }),
|
||||
margin: { top: 14 },
|
||||
padding: {
|
||||
left: sidePadding,
|
||||
right: sidePadding,
|
||||
},
|
||||
background: background(layer, "default"), // posiewic: breaking change
|
||||
},
|
||||
state: {
|
||||
hovered: {
|
||||
background: background(layer, "hovered"),
|
||||
},
|
||||
clicked: {
|
||||
background: background(layer, "pressed"),
|
||||
},
|
||||
}, // hack, we want headerRow to be interactive for whatever reason. It probably shouldn't be interactive in the first place.
|
||||
}),
|
||||
state: {
|
||||
active: {
|
||||
default: {
|
||||
...text(layer, "mono", "active", { size: "sm" }),
|
||||
background: background(layer, "active"),
|
||||
},
|
||||
hovered: {
|
||||
background: background(layer, "hovered"),
|
||||
},
|
||||
clicked: {
|
||||
background: background(layer, "pressed"),
|
||||
},
|
||||
},
|
||||
},
|
||||
active: {
|
||||
...text(layer, "mono", "active", { size: "sm" }),
|
||||
background: background(layer, "active"),
|
||||
}),
|
||||
leaveCall: interactive({
|
||||
base: {
|
||||
background: background(layer),
|
||||
border: border(layer),
|
||||
cornerRadius: 6,
|
||||
margin: {
|
||||
top: 1,
|
||||
},
|
||||
padding: {
|
||||
top: 1,
|
||||
bottom: 1,
|
||||
left: 7,
|
||||
right: 7,
|
||||
},
|
||||
...text(layer, "sans", "variant", { size: "xs" }),
|
||||
},
|
||||
},
|
||||
leaveCall: {
|
||||
background: background(layer),
|
||||
border: border(layer),
|
||||
cornerRadius: 6,
|
||||
margin: {
|
||||
top: 1,
|
||||
state: {
|
||||
hovered: {
|
||||
...text(layer, "sans", "hovered", { size: "xs" }),
|
||||
background: background(layer, "hovered"),
|
||||
border: border(layer, "hovered"),
|
||||
},
|
||||
},
|
||||
padding: {
|
||||
top: 1,
|
||||
bottom: 1,
|
||||
left: 7,
|
||||
right: 7,
|
||||
},
|
||||
...text(layer, "sans", "variant", { size: "xs" }),
|
||||
hover: {
|
||||
...text(layer, "sans", "hovered", { size: "xs" }),
|
||||
background: background(layer, "hovered"),
|
||||
border: border(layer, "hovered"),
|
||||
},
|
||||
},
|
||||
}),
|
||||
contactRow: {
|
||||
padding: {
|
||||
left: sidePadding,
|
||||
right: sidePadding,
|
||||
inactive: {
|
||||
default: {
|
||||
padding: {
|
||||
left: sidePadding,
|
||||
right: sidePadding,
|
||||
},
|
||||
},
|
||||
},
|
||||
active: {
|
||||
background: background(layer, "active"),
|
||||
default: {
|
||||
background: background(layer, "active"),
|
||||
padding: {
|
||||
left: sidePadding,
|
||||
right: sidePadding,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
contactAvatar: {
|
||||
cornerRadius: 10,
|
||||
width: 18,
|
||||
|
@ -135,12 +173,14 @@ export default function contactsPanel(colorScheme: ColorScheme) {
|
|||
},
|
||||
},
|
||||
contactButtonSpacing: nameMargin,
|
||||
contactButton: {
|
||||
...contactButton,
|
||||
hover: {
|
||||
background: background(layer, "hovered"),
|
||||
contactButton: interactive({
|
||||
base: { ...contactButton },
|
||||
state: {
|
||||
hovered: {
|
||||
background: background(layer, "hovered"),
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
disabledButton: {
|
||||
...contactButton,
|
||||
background: background(layer, "on"),
|
||||
|
@ -149,34 +189,52 @@ export default function contactsPanel(colorScheme: ColorScheme) {
|
|||
callingIndicator: {
|
||||
...text(layer, "mono", "variant", { size: "xs" }),
|
||||
},
|
||||
treeBranch: {
|
||||
color: borderColor(layer),
|
||||
width: 1,
|
||||
hover: {
|
||||
color: borderColor(layer),
|
||||
treeBranch: toggleable({
|
||||
base: interactive({
|
||||
base: {
|
||||
color: borderColor(layer),
|
||||
width: 1,
|
||||
},
|
||||
state: {
|
||||
hovered: {
|
||||
color: borderColor(layer),
|
||||
},
|
||||
},
|
||||
}),
|
||||
state: {
|
||||
active: {
|
||||
default: {
|
||||
color: borderColor(layer),
|
||||
},
|
||||
},
|
||||
},
|
||||
active: {
|
||||
color: borderColor(layer),
|
||||
}),
|
||||
projectRow: toggleable({
|
||||
base: interactive({
|
||||
base: {
|
||||
...projectRow,
|
||||
background: background(layer),
|
||||
icon: {
|
||||
margin: { left: nameMargin },
|
||||
color: foreground(layer, "variant"),
|
||||
width: 12,
|
||||
},
|
||||
name: {
|
||||
...projectRow.name,
|
||||
...text(layer, "mono", { size: "sm" }),
|
||||
},
|
||||
},
|
||||
state: {
|
||||
hovered: {
|
||||
background: background(layer, "hovered"),
|
||||
},
|
||||
},
|
||||
}),
|
||||
state: {
|
||||
active: {
|
||||
default: { background: background(layer, "active") },
|
||||
},
|
||||
},
|
||||
},
|
||||
projectRow: {
|
||||
...projectRow,
|
||||
background: background(layer),
|
||||
icon: {
|
||||
margin: { left: nameMargin },
|
||||
color: foreground(layer, "variant"),
|
||||
width: 12,
|
||||
},
|
||||
name: {
|
||||
...projectRow.name,
|
||||
...text(layer, "mono", { size: "sm" }),
|
||||
},
|
||||
hover: {
|
||||
background: background(layer, "hovered"),
|
||||
},
|
||||
active: {
|
||||
background: background(layer, "active"),
|
||||
},
|
||||
},
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { ColorScheme } from "../theme/colorScheme"
|
||||
import { background, foreground, text } from "./components"
|
||||
|
||||
import { interactive } from "../element"
|
||||
const avatarSize = 12
|
||||
const headerPadding = 8
|
||||
|
||||
|
@ -21,24 +21,32 @@ export default function contactNotification(colorScheme: ColorScheme): Object {
|
|||
...text(layer, "sans", { size: "xs" }),
|
||||
margin: { left: avatarSize + headerPadding, top: 6, bottom: 6 },
|
||||
},
|
||||
button: {
|
||||
...text(layer, "sans", "on", { size: "xs" }),
|
||||
background: background(layer, "on"),
|
||||
padding: 4,
|
||||
cornerRadius: 6,
|
||||
margin: { left: 6 },
|
||||
hover: {
|
||||
background: background(layer, "on", "hovered"),
|
||||
button: interactive({
|
||||
base: {
|
||||
...text(layer, "sans", "on", { size: "xs" }),
|
||||
background: background(layer, "on"),
|
||||
padding: 4,
|
||||
cornerRadius: 6,
|
||||
margin: { left: 6 },
|
||||
},
|
||||
},
|
||||
|
||||
state: {
|
||||
hovered: {
|
||||
background: background(layer, "on", "hovered"),
|
||||
},
|
||||
},
|
||||
}),
|
||||
|
||||
dismissButton: {
|
||||
color: foreground(layer, "variant"),
|
||||
iconWidth: 8,
|
||||
iconHeight: 8,
|
||||
buttonWidth: 8,
|
||||
buttonHeight: 8,
|
||||
hover: {
|
||||
color: foreground(layer, "hovered"),
|
||||
default: {
|
||||
color: foreground(layer, "variant"),
|
||||
iconWidth: 8,
|
||||
iconHeight: 8,
|
||||
buttonWidth: 8,
|
||||
buttonHeight: 8,
|
||||
hover: {
|
||||
color: foreground(layer, "hovered"),
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import { ColorScheme } from "../theme/colorScheme"
|
||||
import { background, border, borderColor, text } from "./components"
|
||||
import { interactive, toggleable } from "../element"
|
||||
|
||||
export default function contextMenu(colorScheme: ColorScheme) {
|
||||
let layer = colorScheme.middle
|
||||
|
@ -10,37 +11,54 @@ export default function contextMenu(colorScheme: ColorScheme) {
|
|||
shadow: colorScheme.popoverShadow,
|
||||
border: border(layer),
|
||||
keystrokeMargin: 30,
|
||||
item: {
|
||||
iconSpacing: 8,
|
||||
iconWidth: 14,
|
||||
padding: { left: 6, right: 6, top: 2, bottom: 2 },
|
||||
cornerRadius: 6,
|
||||
label: text(layer, "sans", { size: "sm" }),
|
||||
keystroke: {
|
||||
...text(layer, "sans", "variant", {
|
||||
size: "sm",
|
||||
weight: "bold",
|
||||
}),
|
||||
padding: { left: 3, right: 3 },
|
||||
},
|
||||
hover: {
|
||||
background: background(layer, "hovered"),
|
||||
label: text(layer, "sans", "hovered", { size: "sm" }),
|
||||
keystroke: {
|
||||
...text(layer, "sans", "hovered", {
|
||||
size: "sm",
|
||||
weight: "bold",
|
||||
}),
|
||||
padding: { left: 3, right: 3 },
|
||||
item: toggleable({
|
||||
base: interactive({
|
||||
base: {
|
||||
iconSpacing: 8,
|
||||
iconWidth: 14,
|
||||
padding: { left: 6, right: 6, top: 2, bottom: 2 },
|
||||
cornerRadius: 6,
|
||||
label: text(layer, "sans", { size: "sm" }),
|
||||
keystroke: {
|
||||
...text(layer, "sans", "variant", {
|
||||
size: "sm",
|
||||
weight: "bold",
|
||||
}),
|
||||
padding: { left: 3, right: 3 },
|
||||
},
|
||||
},
|
||||
state: {
|
||||
hovered: {
|
||||
background: background(layer, "hovered"),
|
||||
label: text(layer, "sans", "hovered", { size: "sm" }),
|
||||
keystroke: {
|
||||
...text(layer, "sans", "hovered", {
|
||||
size: "sm",
|
||||
weight: "bold",
|
||||
}),
|
||||
padding: { left: 3, right: 3 },
|
||||
},
|
||||
},
|
||||
clicked: {
|
||||
background: background(layer, "pressed"),
|
||||
},
|
||||
},
|
||||
}),
|
||||
state: {
|
||||
active: {
|
||||
default: {
|
||||
background: background(layer, "active"),
|
||||
},
|
||||
hovered: {
|
||||
background: background(layer, "hovered"),
|
||||
},
|
||||
clicked: {
|
||||
background: background(layer, "pressed"),
|
||||
},
|
||||
},
|
||||
},
|
||||
active: {
|
||||
background: background(layer, "active"),
|
||||
},
|
||||
activeHover: {
|
||||
background: background(layer, "active"),
|
||||
},
|
||||
},
|
||||
}),
|
||||
|
||||
separator: {
|
||||
background: borderColor(layer),
|
||||
margin: { top: 2, bottom: 2 },
|
||||
|
|
|
@ -1,60 +1,69 @@
|
|||
import { ColorScheme } from "../theme/colorScheme"
|
||||
import { background, border, foreground, svg, text } from "./components"
|
||||
|
||||
import { interactive } from "../element"
|
||||
export default function copilot(colorScheme: ColorScheme) {
|
||||
let layer = colorScheme.middle
|
||||
|
||||
let content_width = 264
|
||||
|
||||
let ctaButton = {
|
||||
let ctaButton =
|
||||
// Copied from welcome screen. FIXME: Move this into a ZDS component
|
||||
background: background(layer),
|
||||
border: border(layer, "default"),
|
||||
cornerRadius: 4,
|
||||
margin: {
|
||||
top: 4,
|
||||
bottom: 4,
|
||||
left: 8,
|
||||
right: 8,
|
||||
},
|
||||
padding: {
|
||||
top: 3,
|
||||
bottom: 3,
|
||||
left: 7,
|
||||
right: 7,
|
||||
},
|
||||
...text(layer, "sans", "default", { size: "sm" }),
|
||||
hover: {
|
||||
...text(layer, "sans", "default", { size: "sm" }),
|
||||
background: background(layer, "hovered"),
|
||||
border: border(layer, "active"),
|
||||
},
|
||||
}
|
||||
interactive({
|
||||
base: {
|
||||
background: background(layer),
|
||||
border: border(layer, "default"),
|
||||
cornerRadius: 4,
|
||||
margin: {
|
||||
top: 4,
|
||||
bottom: 4,
|
||||
left: 8,
|
||||
right: 8,
|
||||
},
|
||||
padding: {
|
||||
top: 3,
|
||||
bottom: 3,
|
||||
left: 7,
|
||||
right: 7,
|
||||
},
|
||||
...text(layer, "sans", "default", { size: "sm" }),
|
||||
},
|
||||
state: {
|
||||
hovered: {
|
||||
...text(layer, "sans", "default", { size: "sm" }),
|
||||
background: background(layer, "hovered"),
|
||||
border: border(layer, "active"),
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
return {
|
||||
outLinkIcon: {
|
||||
icon: svg(
|
||||
foreground(layer, "variant"),
|
||||
"icons/link_out_12.svg",
|
||||
12,
|
||||
12
|
||||
),
|
||||
container: {
|
||||
cornerRadius: 6,
|
||||
padding: { left: 6 },
|
||||
},
|
||||
hover: {
|
||||
outLinkIcon: interactive({
|
||||
base: {
|
||||
icon: svg(
|
||||
foreground(layer, "hovered"),
|
||||
foreground(layer, "variant"),
|
||||
"icons/link_out_12.svg",
|
||||
12,
|
||||
12
|
||||
),
|
||||
container: {
|
||||
cornerRadius: 6,
|
||||
padding: { left: 6 },
|
||||
},
|
||||
},
|
||||
},
|
||||
state: {
|
||||
hovered: {
|
||||
icon: {
|
||||
color: foreground(layer, "hovered"),
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
|
||||
modal: {
|
||||
titleText: {
|
||||
...text(layer, "sans", { size: "xs", weight: "bold" }),
|
||||
default: {
|
||||
...text(layer, "sans", { size: "xs", weight: "bold" }),
|
||||
},
|
||||
},
|
||||
titlebar: {
|
||||
background: background(colorScheme.lowest),
|
||||
|
@ -75,42 +84,46 @@ export default function copilot(colorScheme: ColorScheme) {
|
|||
bottom: 8,
|
||||
},
|
||||
},
|
||||
closeIcon: {
|
||||
icon: svg(
|
||||
foreground(layer, "variant"),
|
||||
"icons/x_mark_8.svg",
|
||||
8,
|
||||
8
|
||||
),
|
||||
container: {
|
||||
cornerRadius: 2,
|
||||
padding: {
|
||||
top: 4,
|
||||
bottom: 4,
|
||||
left: 4,
|
||||
right: 4,
|
||||
},
|
||||
margin: {
|
||||
right: 0,
|
||||
},
|
||||
},
|
||||
hover: {
|
||||
closeIcon: interactive({
|
||||
base: {
|
||||
icon: svg(
|
||||
foreground(layer, "on"),
|
||||
foreground(layer, "variant"),
|
||||
"icons/x_mark_8.svg",
|
||||
8,
|
||||
8
|
||||
),
|
||||
container: {
|
||||
cornerRadius: 2,
|
||||
padding: {
|
||||
top: 4,
|
||||
bottom: 4,
|
||||
left: 4,
|
||||
right: 4,
|
||||
},
|
||||
margin: {
|
||||
right: 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
clicked: {
|
||||
icon: svg(
|
||||
foreground(layer, "base"),
|
||||
"icons/x_mark_8.svg",
|
||||
8,
|
||||
8
|
||||
),
|
||||
state: {
|
||||
hovered: {
|
||||
icon: svg(
|
||||
foreground(layer, "on"),
|
||||
"icons/x_mark_8.svg",
|
||||
8,
|
||||
8
|
||||
),
|
||||
},
|
||||
clicked: {
|
||||
icon: svg(
|
||||
foreground(layer, "base"),
|
||||
"icons/x_mark_8.svg",
|
||||
8,
|
||||
8
|
||||
),
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
dimensions: {
|
||||
width: 280,
|
||||
height: 280,
|
||||
|
@ -185,28 +198,32 @@ export default function copilot(colorScheme: ColorScheme) {
|
|||
},
|
||||
},
|
||||
right: (content_width * 1) / 3,
|
||||
rightContainer: {
|
||||
border: border(colorScheme.lowest, "inverted", {
|
||||
bottom: false,
|
||||
right: false,
|
||||
top: false,
|
||||
left: true,
|
||||
}),
|
||||
padding: {
|
||||
top: 3,
|
||||
bottom: 5,
|
||||
left: 8,
|
||||
right: 0,
|
||||
},
|
||||
hover: {
|
||||
border: border(layer, "active", {
|
||||
rightContainer: interactive({
|
||||
base: {
|
||||
border: border(colorScheme.lowest, "inverted", {
|
||||
bottom: false,
|
||||
right: false,
|
||||
top: false,
|
||||
left: true,
|
||||
}),
|
||||
padding: {
|
||||
top: 3,
|
||||
bottom: 5,
|
||||
left: 8,
|
||||
right: 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
state: {
|
||||
hovered: {
|
||||
border: border(layer, "active", {
|
||||
bottom: false,
|
||||
right: false,
|
||||
top: false,
|
||||
left: true,
|
||||
}),
|
||||
},
|
||||
},
|
||||
}),
|
||||
},
|
||||
},
|
||||
|
||||
|
|
|
@ -4,6 +4,7 @@ import { background, border, borderColor, foreground, text } from "./components"
|
|||
import hoverPopover from "./hoverPopover"
|
||||
|
||||
import { buildSyntax } from "../theme/syntax"
|
||||
import { interactive, toggleable } from "../element"
|
||||
|
||||
export default function editor(colorScheme: ColorScheme) {
|
||||
const { isLight } = colorScheme
|
||||
|
@ -48,46 +49,76 @@ export default function editor(colorScheme: ColorScheme) {
|
|||
// Inline autocomplete suggestions, Co-pilot suggestions, etc.
|
||||
suggestion: syntax.predictive,
|
||||
codeActions: {
|
||||
indicator: {
|
||||
color: foreground(layer, "variant"),
|
||||
indicator: toggleable({
|
||||
base: interactive({
|
||||
base: {
|
||||
color: foreground(layer, "variant"),
|
||||
},
|
||||
state: {
|
||||
hovered: {
|
||||
color: foreground(layer, "variant", "hovered"),
|
||||
},
|
||||
clicked: {
|
||||
color: foreground(layer, "variant", "pressed"),
|
||||
},
|
||||
},
|
||||
}),
|
||||
state: {
|
||||
active: {
|
||||
default: {
|
||||
color: foreground(layer, "accent"),
|
||||
},
|
||||
hovered: {
|
||||
color: foreground(layer, "accent", "hovered"),
|
||||
},
|
||||
clicked: {
|
||||
color: foreground(layer, "accent", "pressed"),
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
|
||||
clicked: {
|
||||
color: foreground(layer, "base"),
|
||||
},
|
||||
hover: {
|
||||
color: foreground(layer, "on"),
|
||||
},
|
||||
active: {
|
||||
color: foreground(layer, "on"),
|
||||
},
|
||||
},
|
||||
verticalScale: 0.55,
|
||||
},
|
||||
folds: {
|
||||
iconMarginScale: 2.5,
|
||||
foldedIcon: "icons/chevron_right_8.svg",
|
||||
foldableIcon: "icons/chevron_down_8.svg",
|
||||
indicator: {
|
||||
color: foreground(layer, "variant"),
|
||||
|
||||
clicked: {
|
||||
color: foreground(layer, "base"),
|
||||
indicator: toggleable({
|
||||
base: interactive({
|
||||
base: {
|
||||
color: foreground(layer, "variant"),
|
||||
},
|
||||
state: {
|
||||
hovered: {
|
||||
color: foreground(layer, "on"),
|
||||
},
|
||||
clicked: {
|
||||
color: foreground(layer, "base"),
|
||||
},
|
||||
},
|
||||
}),
|
||||
state: {
|
||||
active: {
|
||||
default: {
|
||||
color: foreground(layer, "default"),
|
||||
},
|
||||
hovered: {
|
||||
color: foreground(layer, "variant"),
|
||||
},
|
||||
},
|
||||
},
|
||||
hover: {
|
||||
color: foreground(layer, "on"),
|
||||
},
|
||||
active: {
|
||||
color: foreground(layer, "on"),
|
||||
},
|
||||
},
|
||||
}),
|
||||
ellipses: {
|
||||
textColor: colorScheme.ramps.neutral(0.71).hex(),
|
||||
cornerRadiusFactor: 0.15,
|
||||
background: {
|
||||
// Copied from hover_popover highlight
|
||||
color: colorScheme.ramps.neutral(0.5).alpha(0.0).hex(),
|
||||
default: {
|
||||
color: colorScheme.ramps.neutral(0.5).alpha(0.0).hex(),
|
||||
},
|
||||
|
||||
hover: {
|
||||
hovered: {
|
||||
color: colorScheme.ramps.neutral(0.5).alpha(0.5).hex(),
|
||||
},
|
||||
|
||||
|
@ -223,21 +254,26 @@ export default function editor(colorScheme: ColorScheme) {
|
|||
color: syntax.linkUri.color,
|
||||
underline: syntax.linkUri.underline,
|
||||
},
|
||||
jumpIcon: {
|
||||
color: foreground(layer, "on"),
|
||||
iconWidth: 20,
|
||||
buttonWidth: 20,
|
||||
cornerRadius: 6,
|
||||
padding: {
|
||||
top: 6,
|
||||
bottom: 6,
|
||||
left: 6,
|
||||
right: 6,
|
||||
jumpIcon: interactive({
|
||||
base: {
|
||||
color: foreground(layer, "on"),
|
||||
iconWidth: 20,
|
||||
buttonWidth: 20,
|
||||
cornerRadius: 6,
|
||||
padding: {
|
||||
top: 6,
|
||||
bottom: 6,
|
||||
left: 6,
|
||||
right: 6,
|
||||
},
|
||||
},
|
||||
hover: {
|
||||
background: background(layer, "on", "hovered"),
|
||||
state: {
|
||||
hovered: {
|
||||
background: background(layer, "on", "hovered"),
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
|
||||
scrollbar: {
|
||||
width: 12,
|
||||
minHeightFactor: 1.0,
|
||||
|
|
|
@ -1,35 +1,40 @@
|
|||
import { ColorScheme } from "../theme/colorScheme"
|
||||
import { background, border, text } from "./components"
|
||||
import { interactive } from "../element"
|
||||
|
||||
export default function feedback(colorScheme: ColorScheme) {
|
||||
let layer = colorScheme.highest
|
||||
|
||||
return {
|
||||
submit_button: {
|
||||
...text(layer, "mono", "on"),
|
||||
background: background(layer, "on"),
|
||||
cornerRadius: 6,
|
||||
border: border(layer, "on"),
|
||||
margin: {
|
||||
right: 4,
|
||||
submit_button: interactive({
|
||||
base: {
|
||||
...text(layer, "mono", "on"),
|
||||
background: background(layer, "on"),
|
||||
cornerRadius: 6,
|
||||
border: border(layer, "on"),
|
||||
margin: {
|
||||
right: 4,
|
||||
},
|
||||
padding: {
|
||||
bottom: 2,
|
||||
left: 10,
|
||||
right: 10,
|
||||
top: 2,
|
||||
},
|
||||
},
|
||||
padding: {
|
||||
bottom: 2,
|
||||
left: 10,
|
||||
right: 10,
|
||||
top: 2,
|
||||
state: {
|
||||
clicked: {
|
||||
...text(layer, "mono", "on", "pressed"),
|
||||
background: background(layer, "on", "pressed"),
|
||||
border: border(layer, "on", "pressed"),
|
||||
},
|
||||
hovered: {
|
||||
...text(layer, "mono", "on", "hovered"),
|
||||
background: background(layer, "on", "hovered"),
|
||||
border: border(layer, "on", "hovered"),
|
||||
},
|
||||
},
|
||||
clicked: {
|
||||
...text(layer, "mono", "on", "pressed"),
|
||||
background: background(layer, "on", "pressed"),
|
||||
border: border(layer, "on", "pressed"),
|
||||
},
|
||||
hover: {
|
||||
...text(layer, "mono", "on", "hovered"),
|
||||
background: background(layer, "on", "hovered"),
|
||||
border: border(layer, "on", "hovered"),
|
||||
},
|
||||
},
|
||||
}),
|
||||
button_margin: 8,
|
||||
info_text_default: text(layer, "sans", "default", { size: "xs" }),
|
||||
link_text_default: text(layer, "sans", "default", {
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import { ColorScheme } from "../theme/colorScheme"
|
||||
import { withOpacity } from "../theme/color"
|
||||
import { background, border, text } from "./components"
|
||||
import { interactive, toggleable } from "../element"
|
||||
|
||||
export default function picker(colorScheme: ColorScheme): any {
|
||||
let layer = colorScheme.lowest
|
||||
|
@ -38,35 +39,65 @@ export default function picker(colorScheme: ColorScheme): any {
|
|||
...container,
|
||||
padding: {},
|
||||
},
|
||||
item: {
|
||||
padding: {
|
||||
bottom: 4,
|
||||
left: 12,
|
||||
right: 12,
|
||||
top: 4,
|
||||
item: toggleable({
|
||||
base: interactive({
|
||||
base: {
|
||||
padding: {
|
||||
bottom: 4,
|
||||
left: 12,
|
||||
right: 12,
|
||||
top: 4,
|
||||
},
|
||||
margin: {
|
||||
top: 1,
|
||||
left: 4,
|
||||
right: 4,
|
||||
},
|
||||
cornerRadius: 8,
|
||||
text: text(layer, "sans", "variant"),
|
||||
highlightText: text(layer, "sans", "accent", {
|
||||
weight: "bold",
|
||||
}),
|
||||
},
|
||||
state: {
|
||||
hovered: {
|
||||
background: withOpacity(
|
||||
background(layer, "hovered"),
|
||||
0.5
|
||||
),
|
||||
},
|
||||
clicked: {
|
||||
background: withOpacity(
|
||||
background(layer, "pressed"),
|
||||
0.5
|
||||
),
|
||||
},
|
||||
},
|
||||
}),
|
||||
state: {
|
||||
active: {
|
||||
default: {
|
||||
background: withOpacity(
|
||||
background(layer, "base", "active"),
|
||||
0.5
|
||||
),
|
||||
},
|
||||
hovered: {
|
||||
background: withOpacity(
|
||||
background(layer, "hovered"),
|
||||
0.5
|
||||
),
|
||||
},
|
||||
clicked: {
|
||||
background: withOpacity(
|
||||
background(layer, "pressed"),
|
||||
0.5
|
||||
),
|
||||
},
|
||||
},
|
||||
},
|
||||
margin: {
|
||||
top: 1,
|
||||
left: 4,
|
||||
right: 4,
|
||||
},
|
||||
cornerRadius: 8,
|
||||
text: text(layer, "sans", "variant"),
|
||||
highlightText: text(layer, "sans", "accent", { weight: "bold" }),
|
||||
active: {
|
||||
background: withOpacity(
|
||||
background(layer, "base", "active"),
|
||||
0.5
|
||||
),
|
||||
text: text(layer, "sans", "base", "active"),
|
||||
highlightText: text(layer, "sans", "accent", {
|
||||
weight: "bold",
|
||||
}),
|
||||
},
|
||||
hover: {
|
||||
background: withOpacity(background(layer, "hovered"), 0.5),
|
||||
},
|
||||
},
|
||||
}),
|
||||
|
||||
inputEditor,
|
||||
emptyInputEditor,
|
||||
noMatches: {
|
||||
|
|
|
@ -1,106 +1,196 @@
|
|||
import { ColorScheme } from "../theme/colorScheme"
|
||||
import { withOpacity } from "../theme/color"
|
||||
import { background, border, foreground, text } from "./components"
|
||||
|
||||
import {
|
||||
Border,
|
||||
TextStyle,
|
||||
background,
|
||||
border,
|
||||
foreground,
|
||||
text,
|
||||
} from "./components"
|
||||
import { interactive, toggleable } from "../element"
|
||||
import merge from "ts-deepmerge"
|
||||
export default function projectPanel(colorScheme: ColorScheme) {
|
||||
const { isLight } = colorScheme
|
||||
|
||||
let layer = colorScheme.middle
|
||||
|
||||
let baseEntry = {
|
||||
height: 22,
|
||||
iconColor: foreground(layer, "variant"),
|
||||
iconSize: 7,
|
||||
iconSpacing: 5,
|
||||
type EntryStateProps = {
|
||||
background?: string
|
||||
border?: Border
|
||||
text?: TextStyle
|
||||
iconColor?: string
|
||||
}
|
||||
|
||||
let status = {
|
||||
git: {
|
||||
modified: isLight
|
||||
? colorScheme.ramps.yellow(0.6).hex()
|
||||
: colorScheme.ramps.yellow(0.5).hex(),
|
||||
inserted: isLight
|
||||
? colorScheme.ramps.green(0.45).hex()
|
||||
: colorScheme.ramps.green(0.5).hex(),
|
||||
conflict: isLight
|
||||
? colorScheme.ramps.red(0.6).hex()
|
||||
: colorScheme.ramps.red(0.5).hex(),
|
||||
},
|
||||
type EntryState = {
|
||||
default: EntryStateProps
|
||||
hovered?: EntryStateProps
|
||||
clicked?: EntryStateProps
|
||||
}
|
||||
|
||||
let entry = {
|
||||
...baseEntry,
|
||||
text: text(layer, "mono", "variant", { size: "sm" }),
|
||||
hover: {
|
||||
background: background(layer, "variant", "hovered"),
|
||||
},
|
||||
active: {
|
||||
background: colorScheme.isLight
|
||||
? withOpacity(background(layer, "active"), 0.5)
|
||||
: background(layer, "active"),
|
||||
text: text(layer, "mono", "active", { size: "sm" }),
|
||||
},
|
||||
activeHover: {
|
||||
background: background(layer, "active"),
|
||||
text: text(layer, "mono", "active", { size: "sm" }),
|
||||
},
|
||||
status,
|
||||
const entry = (unselected?: EntryState, selected?: EntryState) => {
|
||||
const git_status = {
|
||||
git: {
|
||||
modified: isLight
|
||||
? colorScheme.ramps.yellow(0.6).hex()
|
||||
: colorScheme.ramps.yellow(0.5).hex(),
|
||||
inserted: isLight
|
||||
? colorScheme.ramps.green(0.45).hex()
|
||||
: colorScheme.ramps.green(0.5).hex(),
|
||||
conflict: isLight
|
||||
? colorScheme.ramps.red(0.6).hex()
|
||||
: colorScheme.ramps.red(0.5).hex(),
|
||||
},
|
||||
}
|
||||
|
||||
const base_properties = {
|
||||
height: 22,
|
||||
background: background(layer),
|
||||
iconColor: foreground(layer, "variant"),
|
||||
iconSize: 7,
|
||||
iconSpacing: 5,
|
||||
text: text(layer, "sans", "variant", { size: "sm" }),
|
||||
status: {
|
||||
...git_status,
|
||||
},
|
||||
}
|
||||
|
||||
const selectedStyle: EntryState | undefined = selected
|
||||
? selected
|
||||
: unselected
|
||||
|
||||
const unselected_default_style = merge(
|
||||
base_properties,
|
||||
unselected?.default ?? {},
|
||||
{}
|
||||
)
|
||||
const unselected_hovered_style = merge(
|
||||
base_properties,
|
||||
{ background: background(layer, "hovered") },
|
||||
unselected?.hovered ?? {},
|
||||
)
|
||||
const unselected_clicked_style = merge(
|
||||
base_properties,
|
||||
{ background: background(layer, "pressed") },
|
||||
unselected?.clicked ?? {},
|
||||
)
|
||||
const selected_default_style = merge(
|
||||
base_properties,
|
||||
{
|
||||
background: background(colorScheme.lowest),
|
||||
text: text(colorScheme.lowest, "sans", { size: "sm" }),
|
||||
},
|
||||
selectedStyle?.default ?? {},
|
||||
)
|
||||
const selected_hovered_style = merge(
|
||||
base_properties,
|
||||
{
|
||||
background: background(colorScheme.lowest, "hovered"),
|
||||
text: text(colorScheme.lowest, "sans", { size: "sm" }),
|
||||
},
|
||||
selectedStyle?.hovered ?? {},
|
||||
)
|
||||
const selected_clicked_style = merge(
|
||||
base_properties,
|
||||
{
|
||||
background: background(colorScheme.lowest, "pressed"),
|
||||
text: text(colorScheme.lowest, "sans", { size: "sm" }),
|
||||
},
|
||||
selectedStyle?.clicked ?? {},
|
||||
)
|
||||
|
||||
return toggleable({
|
||||
state: {
|
||||
inactive: interactive({
|
||||
state: {
|
||||
default: unselected_default_style,
|
||||
hovered: unselected_hovered_style,
|
||||
clicked: unselected_clicked_style,
|
||||
},
|
||||
}),
|
||||
active: interactive({
|
||||
state: {
|
||||
default: selected_default_style,
|
||||
hovered: selected_hovered_style,
|
||||
clicked: selected_clicked_style,
|
||||
},
|
||||
}),
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
const defaultEntry = entry()
|
||||
|
||||
return {
|
||||
openProjectButton: {
|
||||
background: background(layer),
|
||||
border: border(layer, "active"),
|
||||
cornerRadius: 4,
|
||||
margin: {
|
||||
top: 16,
|
||||
left: 16,
|
||||
right: 16,
|
||||
},
|
||||
padding: {
|
||||
top: 3,
|
||||
bottom: 3,
|
||||
left: 7,
|
||||
right: 7,
|
||||
},
|
||||
...text(layer, "sans", "default", { size: "sm" }),
|
||||
hover: {
|
||||
...text(layer, "sans", "default", { size: "sm" }),
|
||||
background: background(layer, "hovered"),
|
||||
openProjectButton: interactive({
|
||||
base: {
|
||||
background: background(layer),
|
||||
border: border(layer, "active"),
|
||||
cornerRadius: 4,
|
||||
margin: {
|
||||
top: 16,
|
||||
left: 16,
|
||||
right: 16,
|
||||
},
|
||||
padding: {
|
||||
top: 3,
|
||||
bottom: 3,
|
||||
left: 7,
|
||||
right: 7,
|
||||
},
|
||||
...text(layer, "sans", "default", { size: "sm" }),
|
||||
},
|
||||
},
|
||||
state: {
|
||||
hovered: {
|
||||
...text(layer, "sans", "default", { size: "sm" }),
|
||||
background: background(layer, "hovered"),
|
||||
border: border(layer, "active"),
|
||||
},
|
||||
clicked: {
|
||||
...text(layer, "sans", "default", { size: "sm" }),
|
||||
background: background(layer, "pressed"),
|
||||
border: border(layer, "active"),
|
||||
},
|
||||
},
|
||||
}),
|
||||
background: background(layer),
|
||||
padding: { left: 6, right: 6, top: 0, bottom: 6 },
|
||||
indentWidth: 12,
|
||||
entry,
|
||||
entry: defaultEntry,
|
||||
draggedEntry: {
|
||||
...baseEntry,
|
||||
text: text(layer, "mono", "on", { size: "sm" }),
|
||||
...defaultEntry.inactive.default,
|
||||
text: text(layer, "sans", "on", { size: "sm" }),
|
||||
background: withOpacity(background(layer, "on"), 0.9),
|
||||
border: border(layer),
|
||||
status,
|
||||
},
|
||||
ignoredEntry: {
|
||||
...entry,
|
||||
iconColor: foreground(layer, "disabled"),
|
||||
text: text(layer, "mono", "disabled"),
|
||||
active: {
|
||||
...entry.active,
|
||||
iconColor: foreground(layer, "variant"),
|
||||
ignoredEntry: entry(
|
||||
{
|
||||
default: {
|
||||
text: text(layer, "sans", "disabled"),
|
||||
},
|
||||
},
|
||||
},
|
||||
cutEntry: {
|
||||
...entry,
|
||||
text: text(layer, "mono", "disabled"),
|
||||
active: {
|
||||
background: background(layer, "active"),
|
||||
text: text(layer, "mono", "disabled", { size: "sm" }),
|
||||
{
|
||||
default: {
|
||||
iconColor: foreground(layer, "variant"),
|
||||
},
|
||||
}
|
||||
),
|
||||
cutEntry: entry(
|
||||
{
|
||||
default: {
|
||||
text: text(layer, "sans", "disabled"),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
default: {
|
||||
background: background(layer, "active"),
|
||||
text: text(layer, "sans", "disabled", { size: "sm" }),
|
||||
},
|
||||
}
|
||||
),
|
||||
filenameEditor: {
|
||||
background: background(layer, "on"),
|
||||
text: text(layer, "mono", "on", { size: "sm" }),
|
||||
text: text(layer, "sans", "on", { size: "sm" }),
|
||||
selection: colorScheme.players[0],
|
||||
},
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import { ColorScheme } from "../theme/colorScheme"
|
||||
import { withOpacity } from "../theme/color"
|
||||
import { background, border, foreground, text } from "./components"
|
||||
import { interactive, toggleable } from "../element"
|
||||
|
||||
export default function search(colorScheme: ColorScheme) {
|
||||
let layer = colorScheme.highest
|
||||
|
@ -35,36 +36,50 @@ export default function search(colorScheme: ColorScheme) {
|
|||
return {
|
||||
// TODO: Add an activeMatchBackground on the rust side to differentiate between active and inactive
|
||||
matchBackground: withOpacity(foreground(layer, "accent"), 0.4),
|
||||
optionButton: {
|
||||
...text(layer, "mono", "on"),
|
||||
background: background(layer, "on"),
|
||||
cornerRadius: 6,
|
||||
border: border(layer, "on"),
|
||||
margin: {
|
||||
right: 4,
|
||||
optionButton: toggleable({
|
||||
base: interactive({
|
||||
base: {
|
||||
...text(layer, "mono", "on"),
|
||||
background: background(layer, "on"),
|
||||
cornerRadius: 6,
|
||||
border: border(layer, "on"),
|
||||
margin: {
|
||||
right: 4,
|
||||
},
|
||||
padding: {
|
||||
bottom: 2,
|
||||
left: 10,
|
||||
right: 10,
|
||||
top: 2,
|
||||
},
|
||||
},
|
||||
state: {
|
||||
hovered: {
|
||||
...text(layer, "mono", "on", "hovered"),
|
||||
background: background(layer, "on", "hovered"),
|
||||
border: border(layer, "on", "hovered"),
|
||||
},
|
||||
clicked: {
|
||||
...text(layer, "mono", "on", "pressed"),
|
||||
background: background(layer, "on", "pressed"),
|
||||
border: border(layer, "on", "pressed"),
|
||||
},
|
||||
},
|
||||
}),
|
||||
state: {
|
||||
active: {
|
||||
default: {
|
||||
...text(layer, "mono", "accent"),
|
||||
},
|
||||
hovered: {
|
||||
...text(layer, "mono", "accent", "hovered"),
|
||||
},
|
||||
clicked: {
|
||||
...text(layer, "mono", "accent", "pressed"),
|
||||
},
|
||||
},
|
||||
},
|
||||
padding: {
|
||||
bottom: 2,
|
||||
left: 10,
|
||||
right: 10,
|
||||
top: 2,
|
||||
},
|
||||
active: {
|
||||
...text(layer, "mono", "on", "inverted"),
|
||||
background: background(layer, "on", "inverted"),
|
||||
border: border(layer, "on", "inverted"),
|
||||
},
|
||||
clicked: {
|
||||
...text(layer, "mono", "on", "pressed"),
|
||||
background: background(layer, "on", "pressed"),
|
||||
border: border(layer, "on", "pressed"),
|
||||
},
|
||||
hover: {
|
||||
...text(layer, "mono", "on", "hovered"),
|
||||
background: background(layer, "on", "hovered"),
|
||||
border: border(layer, "on", "hovered"),
|
||||
},
|
||||
},
|
||||
}),
|
||||
editor,
|
||||
invalidEditor: {
|
||||
...editor,
|
||||
|
@ -97,17 +112,24 @@ export default function search(colorScheme: ColorScheme) {
|
|||
...text(layer, "mono", "on"),
|
||||
size: 18,
|
||||
},
|
||||
dismissButton: {
|
||||
color: foreground(layer, "variant"),
|
||||
iconWidth: 12,
|
||||
buttonWidth: 14,
|
||||
padding: {
|
||||
left: 10,
|
||||
right: 10,
|
||||
dismissButton: interactive({
|
||||
base: {
|
||||
color: foreground(layer, "variant"),
|
||||
iconWidth: 12,
|
||||
buttonWidth: 14,
|
||||
padding: {
|
||||
left: 10,
|
||||
right: 10,
|
||||
},
|
||||
},
|
||||
hover: {
|
||||
color: foreground(layer, "hovered"),
|
||||
state: {
|
||||
hovered: {
|
||||
color: foreground(layer, "hovered"),
|
||||
},
|
||||
clicked: {
|
||||
color: foreground(layer, "pressed"),
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import { ColorScheme } from "../theme/colorScheme"
|
||||
import { background, border, foreground, text } from "./components"
|
||||
import { interactive } from "../element"
|
||||
|
||||
const headerPadding = 8
|
||||
|
||||
|
@ -12,33 +13,41 @@ export default function simpleMessageNotification(
|
|||
...text(layer, "sans", { size: "xs" }),
|
||||
margin: { left: headerPadding, right: headerPadding },
|
||||
},
|
||||
actionMessage: {
|
||||
...text(layer, "sans", { size: "xs" }),
|
||||
border: border(layer, "active"),
|
||||
cornerRadius: 4,
|
||||
padding: {
|
||||
top: 3,
|
||||
bottom: 3,
|
||||
left: 7,
|
||||
right: 7,
|
||||
},
|
||||
|
||||
margin: { left: headerPadding, top: 6, bottom: 6 },
|
||||
hover: {
|
||||
...text(layer, "sans", "default", { size: "xs" }),
|
||||
background: background(layer, "hovered"),
|
||||
actionMessage: interactive({
|
||||
base: {
|
||||
...text(layer, "sans", { size: "xs" }),
|
||||
border: border(layer, "active"),
|
||||
cornerRadius: 4,
|
||||
padding: {
|
||||
top: 3,
|
||||
bottom: 3,
|
||||
left: 7,
|
||||
right: 7,
|
||||
},
|
||||
|
||||
margin: { left: headerPadding, top: 6, bottom: 6 },
|
||||
},
|
||||
},
|
||||
dismissButton: {
|
||||
color: foreground(layer),
|
||||
iconWidth: 8,
|
||||
iconHeight: 8,
|
||||
buttonWidth: 8,
|
||||
buttonHeight: 8,
|
||||
hover: {
|
||||
color: foreground(layer, "hovered"),
|
||||
state: {
|
||||
hovered: {
|
||||
...text(layer, "sans", "default", { size: "xs" }),
|
||||
background: background(layer, "hovered"),
|
||||
border: border(layer, "active"),
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
dismissButton: interactive({
|
||||
base: {
|
||||
color: foreground(layer),
|
||||
iconWidth: 8,
|
||||
iconHeight: 8,
|
||||
buttonWidth: 8,
|
||||
buttonHeight: 8,
|
||||
},
|
||||
state: {
|
||||
hovered: {
|
||||
color: foreground(layer, "hovered"),
|
||||
},
|
||||
},
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { ColorScheme } from "../theme/colorScheme"
|
||||
import { background, border, foreground, text } from "./components"
|
||||
|
||||
import { interactive, toggleable } from "../element"
|
||||
export default function statusBar(colorScheme: ColorScheme) {
|
||||
let layer = colorScheme.lowest
|
||||
|
||||
|
@ -25,95 +25,123 @@ export default function statusBar(colorScheme: ColorScheme) {
|
|||
},
|
||||
border: border(layer, { top: true, overlay: true }),
|
||||
cursorPosition: text(layer, "sans", "variant"),
|
||||
activeLanguage: {
|
||||
padding: { left: 6, right: 6 },
|
||||
...text(layer, "sans", "variant"),
|
||||
hover: {
|
||||
...text(layer, "sans", "on"),
|
||||
activeLanguage: interactive({
|
||||
base: {
|
||||
padding: { left: 6, right: 6 },
|
||||
...text(layer, "sans", "variant"),
|
||||
},
|
||||
},
|
||||
state: {
|
||||
hovered: {
|
||||
...text(layer, "sans", "on"),
|
||||
},
|
||||
},
|
||||
}),
|
||||
autoUpdateProgressMessage: text(layer, "sans", "variant"),
|
||||
autoUpdateDoneMessage: text(layer, "sans", "variant"),
|
||||
lspStatus: {
|
||||
...diagnosticStatusContainer,
|
||||
iconSpacing: 4,
|
||||
iconWidth: 14,
|
||||
height: 18,
|
||||
message: text(layer, "sans"),
|
||||
iconColor: foreground(layer),
|
||||
hover: {
|
||||
lspStatus: interactive({
|
||||
base: {
|
||||
...diagnosticStatusContainer,
|
||||
iconSpacing: 4,
|
||||
iconWidth: 14,
|
||||
height: 18,
|
||||
message: text(layer, "sans"),
|
||||
iconColor: foreground(layer),
|
||||
background: background(layer, "hovered"),
|
||||
},
|
||||
},
|
||||
diagnosticMessage: {
|
||||
...text(layer, "sans"),
|
||||
hover: text(layer, "sans", "hovered"),
|
||||
},
|
||||
diagnosticSummary: {
|
||||
height: 20,
|
||||
iconWidth: 16,
|
||||
iconSpacing: 2,
|
||||
summarySpacing: 6,
|
||||
text: text(layer, "sans", { size: "sm" }),
|
||||
iconColorOk: foreground(layer, "variant"),
|
||||
iconColorWarning: foreground(layer, "warning"),
|
||||
iconColorError: foreground(layer, "negative"),
|
||||
containerOk: {
|
||||
cornerRadius: 6,
|
||||
padding: { top: 3, bottom: 3, left: 7, right: 7 },
|
||||
state: {
|
||||
hovered: {
|
||||
message: text(layer, "sans"),
|
||||
iconColor: foreground(layer),
|
||||
background: background(layer, "hovered"),
|
||||
},
|
||||
},
|
||||
containerWarning: {
|
||||
...diagnosticStatusContainer,
|
||||
background: background(layer, "warning"),
|
||||
border: border(layer, "warning"),
|
||||
}),
|
||||
diagnosticMessage: interactive({
|
||||
base: {
|
||||
...text(layer, "sans"),
|
||||
},
|
||||
containerError: {
|
||||
...diagnosticStatusContainer,
|
||||
background: background(layer, "negative"),
|
||||
border: border(layer, "negative"),
|
||||
},
|
||||
hover: {
|
||||
iconColorOk: foreground(layer, "on"),
|
||||
state: { hovered: text(layer, "sans", "hovered") },
|
||||
}),
|
||||
diagnosticSummary: interactive({
|
||||
base: {
|
||||
height: 20,
|
||||
iconWidth: 16,
|
||||
iconSpacing: 2,
|
||||
summarySpacing: 6,
|
||||
text: text(layer, "sans", { size: "sm" }),
|
||||
iconColorOk: foreground(layer, "variant"),
|
||||
iconColorWarning: foreground(layer, "warning"),
|
||||
iconColorError: foreground(layer, "negative"),
|
||||
containerOk: {
|
||||
cornerRadius: 6,
|
||||
padding: { top: 3, bottom: 3, left: 7, right: 7 },
|
||||
background: background(layer, "on", "hovered"),
|
||||
},
|
||||
containerWarning: {
|
||||
...diagnosticStatusContainer,
|
||||
background: background(layer, "warning", "hovered"),
|
||||
border: border(layer, "warning", "hovered"),
|
||||
background: background(layer, "warning"),
|
||||
border: border(layer, "warning"),
|
||||
},
|
||||
containerError: {
|
||||
...diagnosticStatusContainer,
|
||||
background: background(layer, "negative", "hovered"),
|
||||
border: border(layer, "negative", "hovered"),
|
||||
background: background(layer, "negative"),
|
||||
border: border(layer, "negative"),
|
||||
},
|
||||
},
|
||||
},
|
||||
state: {
|
||||
hovered: {
|
||||
iconColorOk: foreground(layer, "on"),
|
||||
containerOk: {
|
||||
background: background(layer, "on", "hovered"),
|
||||
},
|
||||
containerWarning: {
|
||||
background: background(layer, "warning", "hovered"),
|
||||
border: border(layer, "warning", "hovered"),
|
||||
},
|
||||
containerError: {
|
||||
background: background(layer, "negative", "hovered"),
|
||||
border: border(layer, "negative", "hovered"),
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
panelButtons: {
|
||||
groupLeft: {},
|
||||
groupBottom: {},
|
||||
groupRight: {},
|
||||
button: {
|
||||
...statusContainer,
|
||||
iconSize: 16,
|
||||
iconColor: foreground(layer, "variant"),
|
||||
label: {
|
||||
margin: { left: 6 },
|
||||
...text(layer, "sans", { size: "sm" }),
|
||||
button: toggleable({
|
||||
base: interactive({
|
||||
base: {
|
||||
...statusContainer,
|
||||
iconSize: 16,
|
||||
iconColor: foreground(layer, "variant"),
|
||||
label: {
|
||||
margin: { left: 6 },
|
||||
...text(layer, "sans", { size: "sm" }),
|
||||
},
|
||||
},
|
||||
state: {
|
||||
hovered: {
|
||||
iconColor: foreground(layer, "hovered"),
|
||||
background: background(layer, "variant"),
|
||||
},
|
||||
},
|
||||
}),
|
||||
state: {
|
||||
active: {
|
||||
default: {
|
||||
iconColor: foreground(layer, "active"),
|
||||
background: background(layer, "active"),
|
||||
},
|
||||
hovered: {
|
||||
iconColor: foreground(layer, "hovered"),
|
||||
background: background(layer, "hovered"),
|
||||
},
|
||||
clicked: {
|
||||
iconColor: foreground(layer, "pressed"),
|
||||
background: background(layer, "pressed"),
|
||||
},
|
||||
},
|
||||
},
|
||||
hover: {
|
||||
iconColor: foreground(layer, "hovered"),
|
||||
background: background(layer, "variant"),
|
||||
},
|
||||
active: {
|
||||
iconColor: foreground(layer, "active"),
|
||||
background: background(layer, "active"),
|
||||
},
|
||||
},
|
||||
}),
|
||||
badge: {
|
||||
cornerRadius: 3,
|
||||
padding: 2,
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import { ColorScheme } from "../theme/colorScheme"
|
||||
import { withOpacity } from "../theme/color"
|
||||
import { text, border, background, foreground } from "./components"
|
||||
import { interactive, toggleable } from "../element"
|
||||
|
||||
export default function tabBar(colorScheme: ColorScheme) {
|
||||
const height = 32
|
||||
|
@ -87,17 +88,36 @@ export default function tabBar(colorScheme: ColorScheme) {
|
|||
inactiveTab: inactivePaneInactiveTab,
|
||||
},
|
||||
draggedTab,
|
||||
paneButton: {
|
||||
color: foreground(layer, "variant"),
|
||||
iconWidth: 12,
|
||||
buttonWidth: activePaneActiveTab.height,
|
||||
hover: {
|
||||
color: foreground(layer, "hovered"),
|
||||
paneButton: toggleable({
|
||||
base: interactive({
|
||||
base: {
|
||||
color: foreground(layer, "variant"),
|
||||
iconWidth: 12,
|
||||
buttonWidth: activePaneActiveTab.height,
|
||||
},
|
||||
state: {
|
||||
hovered: {
|
||||
color: foreground(layer, "hovered"),
|
||||
},
|
||||
clicked: {
|
||||
color: foreground(layer, "pressed"),
|
||||
},
|
||||
},
|
||||
}),
|
||||
state: {
|
||||
active: {
|
||||
default: {
|
||||
color: foreground(layer, "accent"),
|
||||
},
|
||||
hovered: {
|
||||
color: foreground(layer, "hovered"),
|
||||
},
|
||||
clicked: {
|
||||
color: foreground(layer, "pressed"),
|
||||
},
|
||||
},
|
||||
},
|
||||
active: {
|
||||
color: foreground(layer, "accent"),
|
||||
},
|
||||
},
|
||||
}),
|
||||
paneButtonContainer: {
|
||||
background: tab.background,
|
||||
border: {
|
||||
|
|
266
styles/src/styleTree/titlebar.ts
Normal file
266
styles/src/styleTree/titlebar.ts
Normal file
|
@ -0,0 +1,266 @@
|
|||
import { ColorScheme } from "../common"
|
||||
import { icon_button, toggleable_icon_button } from "../component/icon_button"
|
||||
import { toggleable_text_button } from "../component/text_button"
|
||||
import { interactive, toggleable } from "../element"
|
||||
import { withOpacity } from "../theme/color"
|
||||
import { background, border, foreground, text } from "./components"
|
||||
|
||||
const ITEM_SPACING = 8
|
||||
const TITLEBAR_HEIGHT = 32
|
||||
|
||||
function build_spacing(
|
||||
container_height: number,
|
||||
element_height: number,
|
||||
spacing: number
|
||||
) {
|
||||
return {
|
||||
group: spacing,
|
||||
item: spacing / 2,
|
||||
half_item: spacing / 4,
|
||||
marginY: (container_height - element_height) / 2,
|
||||
marginX: (container_height - element_height) / 2,
|
||||
}
|
||||
}
|
||||
|
||||
function call_controls(theme: ColorScheme) {
|
||||
const button_height = 18
|
||||
|
||||
const space = build_spacing(TITLEBAR_HEIGHT, button_height, ITEM_SPACING)
|
||||
const marginY = {
|
||||
top: space.marginY,
|
||||
bottom: space.marginY,
|
||||
}
|
||||
|
||||
return {
|
||||
toggle_microphone_button: toggleable_icon_button(theme, {
|
||||
margin: {
|
||||
...marginY,
|
||||
left: space.group,
|
||||
right: space.half_item,
|
||||
},
|
||||
active_color: "negative",
|
||||
}),
|
||||
|
||||
toggle_speakers_button: toggleable_icon_button(theme, {
|
||||
margin: {
|
||||
...marginY,
|
||||
left: space.half_item,
|
||||
right: space.half_item,
|
||||
},
|
||||
}),
|
||||
|
||||
screen_share_button: toggleable_icon_button(theme, {
|
||||
margin: {
|
||||
...marginY,
|
||||
left: space.half_item,
|
||||
right: space.group,
|
||||
},
|
||||
active_color: "accent",
|
||||
}),
|
||||
|
||||
muted: foreground(theme.lowest, "negative"),
|
||||
speaking: foreground(theme.lowest, "accent"),
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens the User Menu when toggled
|
||||
*
|
||||
* When logged in shows the user's avatar and a chevron,
|
||||
* When logged out only shows a chevron.
|
||||
*/
|
||||
function user_menu(theme: ColorScheme) {
|
||||
const button_height = 18
|
||||
|
||||
const space = build_spacing(TITLEBAR_HEIGHT, button_height, ITEM_SPACING)
|
||||
|
||||
const build_button = ({ online }: { online: boolean }) => {
|
||||
const button = toggleable({
|
||||
base: interactive({
|
||||
base: {
|
||||
cornerRadius: 6,
|
||||
height: button_height,
|
||||
width: online ? 37 : 24,
|
||||
padding: {
|
||||
top: 2,
|
||||
bottom: 2,
|
||||
left: 6,
|
||||
right: 6,
|
||||
},
|
||||
margin: {
|
||||
left: space.item,
|
||||
right: space.item,
|
||||
},
|
||||
...text(theme.lowest, "sans", { size: "xs" }),
|
||||
background: background(theme.lowest),
|
||||
},
|
||||
state: {
|
||||
hovered: {
|
||||
...text(theme.lowest, "sans", "hovered", {
|
||||
size: "xs",
|
||||
}),
|
||||
background: background(theme.lowest, "hovered"),
|
||||
},
|
||||
clicked: {
|
||||
...text(theme.lowest, "sans", "pressed", {
|
||||
size: "xs",
|
||||
}),
|
||||
background: background(theme.lowest, "pressed"),
|
||||
},
|
||||
},
|
||||
}),
|
||||
state: {
|
||||
active: {
|
||||
default: {
|
||||
...text(theme.lowest, "sans", "active", { size: "xs" }),
|
||||
background: background(theme.middle),
|
||||
},
|
||||
hovered: {
|
||||
...text(theme.lowest, "sans", "active", { size: "xs" }),
|
||||
background: background(theme.middle, "hovered"),
|
||||
},
|
||||
clicked: {
|
||||
...text(theme.lowest, "sans", "active", { size: "xs" }),
|
||||
background: background(theme.middle, "pressed"),
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
return {
|
||||
user_menu: button,
|
||||
avatar: {
|
||||
icon_width: 16,
|
||||
icon_height: 16,
|
||||
corner_radius: 4,
|
||||
outer_width: 16,
|
||||
outer_corner_radius: 16,
|
||||
},
|
||||
icon: {
|
||||
margin: {
|
||||
top: 2,
|
||||
left: online ? space.item : 0,
|
||||
right: space.group,
|
||||
bottom: 2,
|
||||
},
|
||||
width: 11,
|
||||
height: 11,
|
||||
color: foreground(theme.lowest),
|
||||
},
|
||||
}
|
||||
}
|
||||
return {
|
||||
userMenuButtonOnline: build_button({ online: true }),
|
||||
userMenuButtonOffline: build_button({ online: false }),
|
||||
}
|
||||
}
|
||||
|
||||
export function titlebar(theme: ColorScheme) {
|
||||
const avatarWidth = 15
|
||||
const avatarOuterWidth = avatarWidth + 4
|
||||
const followerAvatarWidth = 14
|
||||
const followerAvatarOuterWidth = followerAvatarWidth + 4
|
||||
|
||||
return {
|
||||
item_spacing: ITEM_SPACING,
|
||||
facePileSpacing: 2,
|
||||
height: TITLEBAR_HEIGHT,
|
||||
background: background(theme.lowest),
|
||||
border: border(theme.lowest, { bottom: true }),
|
||||
padding: {
|
||||
left: 80,
|
||||
right: 0,
|
||||
},
|
||||
|
||||
// Project
|
||||
title: text(theme.lowest, "sans", "variant"),
|
||||
highlight_color: text(theme.lowest, "sans", "active").color,
|
||||
|
||||
// Collaborators
|
||||
leaderAvatar: {
|
||||
width: avatarWidth,
|
||||
outerWidth: avatarOuterWidth,
|
||||
cornerRadius: avatarWidth / 2,
|
||||
outerCornerRadius: avatarOuterWidth / 2,
|
||||
},
|
||||
followerAvatar: {
|
||||
width: followerAvatarWidth,
|
||||
outerWidth: followerAvatarOuterWidth,
|
||||
cornerRadius: followerAvatarWidth / 2,
|
||||
outerCornerRadius: followerAvatarOuterWidth / 2,
|
||||
},
|
||||
inactiveAvatarGrayscale: true,
|
||||
followerAvatarOverlap: 8,
|
||||
leaderSelection: {
|
||||
margin: {
|
||||
top: 4,
|
||||
bottom: 4,
|
||||
},
|
||||
padding: {
|
||||
left: 2,
|
||||
right: 2,
|
||||
top: 2,
|
||||
bottom: 2,
|
||||
},
|
||||
cornerRadius: 6,
|
||||
},
|
||||
avatarRibbon: {
|
||||
height: 3,
|
||||
width: 14,
|
||||
// TODO: Chore: Make avatarRibbon colors driven by the theme rather than being hard coded.
|
||||
},
|
||||
|
||||
sign_in_button: toggleable_text_button(theme, {}),
|
||||
offlineIcon: {
|
||||
color: foreground(theme.lowest, "variant"),
|
||||
width: 16,
|
||||
margin: {
|
||||
left: ITEM_SPACING,
|
||||
},
|
||||
padding: {
|
||||
right: 4,
|
||||
},
|
||||
},
|
||||
|
||||
// When the collaboration server is out of date, show a warning
|
||||
outdatedWarning: {
|
||||
...text(theme.lowest, "sans", "warning", { size: "xs" }),
|
||||
background: withOpacity(background(theme.lowest, "warning"), 0.3),
|
||||
border: border(theme.lowest, "warning"),
|
||||
margin: {
|
||||
left: ITEM_SPACING,
|
||||
},
|
||||
padding: {
|
||||
left: 8,
|
||||
right: 8,
|
||||
},
|
||||
cornerRadius: 6,
|
||||
},
|
||||
|
||||
leave_call_button: icon_button(theme, {
|
||||
margin: {
|
||||
left: ITEM_SPACING / 2,
|
||||
right: ITEM_SPACING,
|
||||
},
|
||||
}),
|
||||
|
||||
...call_controls(theme),
|
||||
|
||||
toggle_contacts_button: toggleable_icon_button(theme, {
|
||||
margin: {
|
||||
left: ITEM_SPACING,
|
||||
},
|
||||
}),
|
||||
|
||||
// Jewel that notifies you that there are new contact requests
|
||||
toggleContactsBadge: {
|
||||
cornerRadius: 3,
|
||||
padding: 2,
|
||||
margin: { top: 3, left: 3 },
|
||||
border: border(theme.lowest),
|
||||
background: foreground(theme.lowest, "accent"),
|
||||
},
|
||||
shareButton: toggleable_text_button(theme, {}),
|
||||
user_menu: user_menu(theme),
|
||||
}
|
||||
}
|
47
styles/src/styleTree/toggle.ts
Normal file
47
styles/src/styleTree/toggle.ts
Normal file
|
@ -0,0 +1,47 @@
|
|||
import merge from "ts-deepmerge"
|
||||
|
||||
type ToggleState = "inactive" | "active"
|
||||
|
||||
type Toggleable<T> = Record<ToggleState, T>
|
||||
|
||||
const NO_INACTIVE_OR_BASE_ERROR =
|
||||
"A toggleable object must have an inactive state, or a base property."
|
||||
const NO_ACTIVE_ERROR = "A toggleable object must have an active state."
|
||||
|
||||
interface ToggleableProps<T> {
|
||||
base?: T
|
||||
state: Partial<Record<ToggleState, T>>
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function for creating Toggleable objects.
|
||||
* @template T The type of the object being toggled.
|
||||
* @param props Object containing the base (inactive) state and state modifications to create the active state.
|
||||
* @returns A Toggleable object containing both the inactive and active states.
|
||||
* @example
|
||||
* ```
|
||||
* toggleable({
|
||||
* base: { background: "#000000", text: "#CCCCCC" },
|
||||
* state: { active: { text: "#CCCCCC" } },
|
||||
* })
|
||||
* ```
|
||||
*/
|
||||
export function toggleable<T extends object>(
|
||||
props: ToggleableProps<T>
|
||||
): Toggleable<T> {
|
||||
const { base, state } = props
|
||||
|
||||
if (!base && !state.inactive) throw new Error(NO_INACTIVE_OR_BASE_ERROR)
|
||||
if (!state.active) throw new Error(NO_ACTIVE_ERROR)
|
||||
|
||||
const inactiveState = base
|
||||
? ((state.inactive ? merge(base, state.inactive) : base) as T)
|
||||
: (state.inactive as T)
|
||||
|
||||
const toggleObj: Toggleable<T> = {
|
||||
inactive: inactiveState,
|
||||
active: merge(base ?? {}, state.active) as T,
|
||||
}
|
||||
|
||||
return toggleObj
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
import { ColorScheme } from "../theme/colorScheme"
|
||||
import { background, border, text } from "./components"
|
||||
|
||||
import { interactive, toggleable } from "../element"
|
||||
export default function dropdownMenu(colorScheme: ColorScheme) {
|
||||
let layer = colorScheme.middle
|
||||
|
||||
|
@ -9,38 +9,56 @@ export default function dropdownMenu(colorScheme: ColorScheme) {
|
|||
background: background(layer),
|
||||
border: border(layer),
|
||||
shadow: colorScheme.popoverShadow,
|
||||
header: {
|
||||
...text(layer, "sans", { size: "sm" }),
|
||||
secondaryText: text(layer, "sans", { size: "sm", color: "#aaaaaa" }),
|
||||
secondaryTextSpacing: 10,
|
||||
padding: { left: 8, right: 8, top: 2, bottom: 2 },
|
||||
cornerRadius: 6,
|
||||
background: background(layer, "on"),
|
||||
border: border(layer, "on", { overlay: true }),
|
||||
hover: {
|
||||
background: background(layer, "hovered"),
|
||||
...text(layer, "sans", "hovered", { size: "sm" }),
|
||||
}
|
||||
},
|
||||
header: interactive({
|
||||
base: {
|
||||
...text(layer, "sans", { size: "sm" }),
|
||||
secondaryText: text(layer, "sans", {
|
||||
size: "sm",
|
||||
color: "#aaaaaa",
|
||||
}),
|
||||
secondaryTextSpacing: 10,
|
||||
padding: { left: 8, right: 8, top: 2, bottom: 2 },
|
||||
cornerRadius: 6,
|
||||
background: background(layer, "on"),
|
||||
},
|
||||
state: {
|
||||
hovered: {
|
||||
background: background(layer, "hovered"),
|
||||
},
|
||||
clicked: {
|
||||
background: background(layer, "pressed"),
|
||||
},
|
||||
},
|
||||
}),
|
||||
sectionHeader: {
|
||||
...text(layer, "sans", { size: "sm" }),
|
||||
padding: { left: 8, right: 8, top: 8, bottom: 8 },
|
||||
},
|
||||
item: {
|
||||
...text(layer, "sans", { size: "sm" }),
|
||||
secondaryTextSpacing: 10,
|
||||
secondaryText: text(layer, "sans", { size: "sm" }),
|
||||
padding: { left: 18, right: 18, top: 2, bottom: 2 },
|
||||
hover: {
|
||||
background: background(layer, "hovered"),
|
||||
...text(layer, "sans", "hovered", { size: "sm" }),
|
||||
item: toggleable({
|
||||
base: interactive({
|
||||
base: {
|
||||
...text(layer, "sans", { size: "sm" }),
|
||||
secondaryTextSpacing: 10,
|
||||
secondaryText: text(layer, "sans", { size: "sm" }),
|
||||
padding: { left: 18, right: 18, top: 2, bottom: 2 },
|
||||
},
|
||||
state: {
|
||||
hovered: {
|
||||
background: background(layer, "hovered"),
|
||||
...text(layer, "sans", "hovered", { size: "sm" }),
|
||||
},
|
||||
},
|
||||
}),
|
||||
state: {
|
||||
active: {
|
||||
default: {
|
||||
background: background(layer, "active"),
|
||||
},
|
||||
hovered: {
|
||||
background: background(layer, "hovered"),
|
||||
},
|
||||
},
|
||||
},
|
||||
active: {
|
||||
background: background(layer, "active"),
|
||||
},
|
||||
activeHover: {
|
||||
background: background(layer, "active"),
|
||||
},
|
||||
},
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import { ColorScheme } from "../theme/colorScheme"
|
||||
import { foreground, text } from "./components"
|
||||
import { interactive } from "../element"
|
||||
|
||||
const headerPadding = 8
|
||||
|
||||
|
@ -10,22 +11,30 @@ export default function updateNotification(colorScheme: ColorScheme): Object {
|
|||
...text(layer, "sans", { size: "xs" }),
|
||||
margin: { left: headerPadding, right: headerPadding },
|
||||
},
|
||||
actionMessage: {
|
||||
...text(layer, "sans", { size: "xs" }),
|
||||
margin: { left: headerPadding, top: 6, bottom: 6 },
|
||||
hover: {
|
||||
color: foreground(layer, "hovered"),
|
||||
actionMessage: interactive({
|
||||
base: {
|
||||
...text(layer, "sans", { size: "xs" }),
|
||||
margin: { left: headerPadding, top: 6, bottom: 6 },
|
||||
},
|
||||
},
|
||||
dismissButton: {
|
||||
color: foreground(layer),
|
||||
iconWidth: 8,
|
||||
iconHeight: 8,
|
||||
buttonWidth: 8,
|
||||
buttonHeight: 8,
|
||||
hover: {
|
||||
color: foreground(layer, "hovered"),
|
||||
state: {
|
||||
hovered: {
|
||||
color: foreground(layer, "hovered"),
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
dismissButton: interactive({
|
||||
base: {
|
||||
color: foreground(layer),
|
||||
iconWidth: 8,
|
||||
iconHeight: 8,
|
||||
buttonWidth: 8,
|
||||
buttonHeight: 8,
|
||||
},
|
||||
state: {
|
||||
hovered: {
|
||||
color: foreground(layer, "hovered"),
|
||||
},
|
||||
},
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@ import {
|
|||
TextProperties,
|
||||
svg,
|
||||
} from "./components"
|
||||
import { interactive } from "../element"
|
||||
|
||||
export default function welcome(colorScheme: ColorScheme) {
|
||||
let layer = colorScheme.highest
|
||||
|
@ -63,27 +64,31 @@ export default function welcome(colorScheme: ColorScheme) {
|
|||
bottom: 2,
|
||||
},
|
||||
},
|
||||
button: {
|
||||
background: background(layer),
|
||||
border: border(layer, "active"),
|
||||
cornerRadius: 4,
|
||||
margin: {
|
||||
top: 4,
|
||||
bottom: 4,
|
||||
},
|
||||
padding: {
|
||||
top: 3,
|
||||
bottom: 3,
|
||||
left: 7,
|
||||
right: 7,
|
||||
},
|
||||
...text(layer, "sans", "default", interactive_text_size),
|
||||
hover: {
|
||||
...text(layer, "sans", "default", interactive_text_size),
|
||||
background: background(layer, "hovered"),
|
||||
button: interactive({
|
||||
base: {
|
||||
background: background(layer),
|
||||
border: border(layer, "active"),
|
||||
cornerRadius: 4,
|
||||
margin: {
|
||||
top: 4,
|
||||
bottom: 4,
|
||||
},
|
||||
padding: {
|
||||
top: 3,
|
||||
bottom: 3,
|
||||
left: 7,
|
||||
right: 7,
|
||||
},
|
||||
...text(layer, "sans", "default", interactive_text_size),
|
||||
},
|
||||
},
|
||||
state: {
|
||||
hovered: {
|
||||
...text(layer, "sans", "default", interactive_text_size),
|
||||
background: background(layer, "hovered"),
|
||||
},
|
||||
},
|
||||
}),
|
||||
|
||||
usageNote: {
|
||||
...text(layer, "sans", "variant", { size: "2xs" }),
|
||||
padding: {
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import { ColorScheme } from "../theme/colorScheme"
|
||||
import { withOpacity } from "../theme/color"
|
||||
import { toggleable } from "../element"
|
||||
import {
|
||||
background,
|
||||
border,
|
||||
|
@ -10,38 +11,53 @@ import {
|
|||
} from "./components"
|
||||
import statusBar from "./statusBar"
|
||||
import tabBar from "./tabBar"
|
||||
|
||||
import { interactive } from "../element"
|
||||
import merge from "ts-deepmerge"
|
||||
export default function workspace(colorScheme: ColorScheme) {
|
||||
const layer = colorScheme.lowest
|
||||
const isLight = colorScheme.isLight
|
||||
const itemSpacing = 8
|
||||
const titlebarButton = {
|
||||
cornerRadius: 6,
|
||||
padding: {
|
||||
top: 1,
|
||||
bottom: 1,
|
||||
left: 8,
|
||||
right: 8,
|
||||
const titlebarButton = toggleable({
|
||||
base: interactive({
|
||||
base: {
|
||||
cornerRadius: 6,
|
||||
padding: {
|
||||
top: 1,
|
||||
bottom: 1,
|
||||
left: 8,
|
||||
right: 8,
|
||||
},
|
||||
...text(layer, "sans", "variant", { size: "xs" }),
|
||||
background: background(layer, "variant"),
|
||||
border: border(layer),
|
||||
},
|
||||
state: {
|
||||
hovered: {
|
||||
...text(layer, "sans", "variant", "hovered", {
|
||||
size: "xs",
|
||||
}),
|
||||
background: background(layer, "variant", "hovered"),
|
||||
border: border(layer, "variant", "hovered"),
|
||||
},
|
||||
clicked: {
|
||||
...text(layer, "sans", "variant", "pressed", {
|
||||
size: "xs",
|
||||
}),
|
||||
background: background(layer, "variant", "pressed"),
|
||||
border: border(layer, "variant", "pressed"),
|
||||
},
|
||||
},
|
||||
}),
|
||||
state: {
|
||||
active: {
|
||||
default: {
|
||||
...text(layer, "sans", "variant", "active", { size: "xs" }),
|
||||
background: background(layer, "variant", "active"),
|
||||
border: border(layer, "variant", "active"),
|
||||
},
|
||||
},
|
||||
},
|
||||
...text(layer, "sans", "variant", { size: "xs" }),
|
||||
background: background(layer, "variant"),
|
||||
border: border(layer),
|
||||
hover: {
|
||||
...text(layer, "sans", "variant", "hovered", { size: "xs" }),
|
||||
background: background(layer, "variant", "hovered"),
|
||||
border: border(layer, "variant", "hovered"),
|
||||
},
|
||||
clicked: {
|
||||
...text(layer, "sans", "variant", "pressed", { size: "xs" }),
|
||||
background: background(layer, "variant", "pressed"),
|
||||
border: border(layer, "variant", "pressed"),
|
||||
},
|
||||
active: {
|
||||
...text(layer, "sans", "variant", "active", { size: "xs" }),
|
||||
background: background(layer, "variant", "active"),
|
||||
border: border(layer, "variant", "active"),
|
||||
},
|
||||
}
|
||||
})
|
||||
const avatarWidth = 18
|
||||
const avatarOuterWidth = avatarWidth + 4
|
||||
const followerAvatarWidth = 14
|
||||
|
@ -78,19 +94,24 @@ export default function workspace(colorScheme: ColorScheme) {
|
|||
},
|
||||
cornerRadius: 4,
|
||||
},
|
||||
keyboardHint: {
|
||||
...text(layer, "sans", "variant", { size: "sm" }),
|
||||
padding: {
|
||||
top: 3,
|
||||
left: 8,
|
||||
right: 8,
|
||||
bottom: 3,
|
||||
keyboardHint: interactive({
|
||||
base: {
|
||||
...text(layer, "sans", "variant", { size: "sm" }),
|
||||
padding: {
|
||||
top: 3,
|
||||
left: 8,
|
||||
right: 8,
|
||||
bottom: 3,
|
||||
},
|
||||
cornerRadius: 8,
|
||||
},
|
||||
cornerRadius: 8,
|
||||
hover: {
|
||||
...text(layer, "sans", "active", { size: "sm" }),
|
||||
state: {
|
||||
hovered: {
|
||||
...text(layer, "sans", "active", { size: "sm" }),
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
|
||||
keyboardHintWidth: 320,
|
||||
},
|
||||
joiningProjectAvatar: {
|
||||
|
@ -201,12 +222,15 @@ export default function workspace(colorScheme: ColorScheme) {
|
|||
|
||||
// Sign in buttom
|
||||
// FlatButton, Variant
|
||||
signInPrompt: {
|
||||
margin: {
|
||||
left: itemSpacing,
|
||||
signInPrompt: merge(titlebarButton, {
|
||||
inactive: {
|
||||
default: {
|
||||
margin: {
|
||||
left: itemSpacing,
|
||||
},
|
||||
},
|
||||
},
|
||||
...titlebarButton,
|
||||
},
|
||||
}),
|
||||
|
||||
// Offline Indicator
|
||||
offlineIcon: {
|
||||
|
@ -234,40 +258,68 @@ export default function workspace(colorScheme: ColorScheme) {
|
|||
},
|
||||
cornerRadius: 6,
|
||||
},
|
||||
callControl: {
|
||||
cornerRadius: 6,
|
||||
color: foreground(layer, "variant"),
|
||||
iconWidth: 12,
|
||||
buttonWidth: 20,
|
||||
hover: {
|
||||
background: background(layer, "variant", "hovered"),
|
||||
color: foreground(layer, "variant", "hovered"),
|
||||
callControl: interactive({
|
||||
base: {
|
||||
cornerRadius: 6,
|
||||
color: foreground(layer, "variant"),
|
||||
iconWidth: 12,
|
||||
buttonWidth: 20,
|
||||
},
|
||||
state: {
|
||||
hovered: {
|
||||
background: background(layer, "variant", "hovered"),
|
||||
color: foreground(layer, "variant", "hovered"),
|
||||
},
|
||||
},
|
||||
}),
|
||||
toggleContactsButton: toggleable({
|
||||
base: interactive({
|
||||
base: {
|
||||
margin: { left: itemSpacing },
|
||||
cornerRadius: 6,
|
||||
color: foreground(layer, "variant"),
|
||||
iconWidth: 14,
|
||||
buttonWidth: 20,
|
||||
},
|
||||
state: {
|
||||
clicked: {
|
||||
background: background(layer, "variant", "pressed"),
|
||||
},
|
||||
hovered: {
|
||||
background: background(layer, "variant", "hovered"),
|
||||
},
|
||||
},
|
||||
}),
|
||||
state: {
|
||||
active: {
|
||||
default: {
|
||||
background: background(layer, "on", "default"),
|
||||
},
|
||||
hovered: {
|
||||
background: background(layer, "on", "hovered"),
|
||||
},
|
||||
clicked: {
|
||||
background: background(layer, "on", "pressed"),
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
userMenuButton: merge(titlebarButton, {
|
||||
inactive: {
|
||||
default: {
|
||||
buttonWidth: 20,
|
||||
iconWidth: 12,
|
||||
},
|
||||
},
|
||||
},
|
||||
toggleContactsButton: {
|
||||
margin: { left: itemSpacing },
|
||||
cornerRadius: 6,
|
||||
color: foreground(layer, "variant"),
|
||||
iconWidth: 14,
|
||||
buttonWidth: 20,
|
||||
active: {
|
||||
background: background(layer, "variant", "active"),
|
||||
color: foreground(layer, "variant", "active"),
|
||||
// posiewic: these properties are not currently set on main
|
||||
default: {
|
||||
iconWidth: 12,
|
||||
button_width: 20,
|
||||
},
|
||||
},
|
||||
clicked: {
|
||||
background: background(layer, "variant", "pressed"),
|
||||
color: foreground(layer, "variant", "pressed"),
|
||||
},
|
||||
hover: {
|
||||
background: background(layer, "variant", "hovered"),
|
||||
color: foreground(layer, "variant", "hovered"),
|
||||
},
|
||||
},
|
||||
userMenuButton: {
|
||||
buttonWidth: 20,
|
||||
iconWidth: 12,
|
||||
...titlebarButton,
|
||||
},
|
||||
}),
|
||||
|
||||
toggleContactsBadge: {
|
||||
cornerRadius: 3,
|
||||
padding: 2,
|
||||
|
@ -285,12 +337,45 @@ export default function workspace(colorScheme: ColorScheme) {
|
|||
background: background(colorScheme.highest),
|
||||
border: border(colorScheme.highest, { bottom: true }),
|
||||
itemSpacing: 8,
|
||||
navButton: {
|
||||
color: foreground(colorScheme.highest, "on"),
|
||||
iconWidth: 12,
|
||||
buttonWidth: 24,
|
||||
navButton: interactive({
|
||||
base: {
|
||||
color: foreground(colorScheme.highest, "on"),
|
||||
iconWidth: 12,
|
||||
buttonWidth: 24,
|
||||
cornerRadius: 6,
|
||||
},
|
||||
state: {
|
||||
hovered: {
|
||||
color: foreground(colorScheme.highest, "on", "hovered"),
|
||||
background: background(
|
||||
colorScheme.highest,
|
||||
"on",
|
||||
"hovered"
|
||||
),
|
||||
},
|
||||
disabled: {
|
||||
color: foreground(
|
||||
colorScheme.highest,
|
||||
"on",
|
||||
"disabled"
|
||||
),
|
||||
},
|
||||
},
|
||||
}),
|
||||
padding: { left: 8, right: 8, top: 4, bottom: 4 },
|
||||
},
|
||||
breadcrumbHeight: 24,
|
||||
breadcrumbs: interactive({
|
||||
base: {
|
||||
...text(colorScheme.highest, "sans", "variant"),
|
||||
cornerRadius: 6,
|
||||
hover: {
|
||||
padding: {
|
||||
left: 6,
|
||||
right: 6,
|
||||
},
|
||||
},
|
||||
state: {
|
||||
hovered: {
|
||||
color: foreground(colorScheme.highest, "on", "hovered"),
|
||||
background: background(
|
||||
colorScheme.highest,
|
||||
|
@ -298,25 +383,8 @@ export default function workspace(colorScheme: ColorScheme) {
|
|||
"hovered"
|
||||
),
|
||||
},
|
||||
disabled: {
|
||||
color: foreground(colorScheme.highest, "on", "disabled"),
|
||||
},
|
||||
},
|
||||
padding: { left: 8, right: 8, top: 4, bottom: 4 },
|
||||
},
|
||||
breadcrumbHeight: 24,
|
||||
breadcrumbs: {
|
||||
...text(colorScheme.highest, "sans", "variant"),
|
||||
cornerRadius: 6,
|
||||
padding: {
|
||||
left: 6,
|
||||
right: 6,
|
||||
},
|
||||
hover: {
|
||||
color: foreground(colorScheme.highest, "on", "hovered"),
|
||||
background: background(colorScheme.highest, "on", "hovered"),
|
||||
},
|
||||
},
|
||||
}),
|
||||
disconnectedOverlay: {
|
||||
...text(layer, "sans"),
|
||||
background: withOpacity(background(layer), 0.8),
|
||||
|
|
|
@ -1,9 +1,19 @@
|
|||
import { SingleBoxShadowToken, SingleColorToken, SingleOtherToken, TokenTypes } from "@tokens-studio/types"
|
||||
import { ColorScheme, Shadow, SyntaxHighlightStyle, ThemeSyntax } from "../colorScheme"
|
||||
import {
|
||||
SingleBoxShadowToken,
|
||||
SingleColorToken,
|
||||
SingleOtherToken,
|
||||
TokenTypes,
|
||||
} from "@tokens-studio/types"
|
||||
import {
|
||||
ColorScheme,
|
||||
Shadow,
|
||||
SyntaxHighlightStyle,
|
||||
ThemeSyntax,
|
||||
} from "../colorScheme"
|
||||
import { LayerToken, layerToken } from "./layer"
|
||||
import { PlayersToken, playersToken } from "./players"
|
||||
import { colorToken } from "./token"
|
||||
import { Syntax } from "../syntax";
|
||||
import { Syntax } from "../syntax"
|
||||
import editor from "../../styleTree/editor"
|
||||
|
||||
interface ColorSchemeTokens {
|
||||
|
@ -18,27 +28,32 @@ interface ColorSchemeTokens {
|
|||
syntax?: Partial<ThemeSyntaxColorTokens>
|
||||
}
|
||||
|
||||
const createShadowToken = (shadow: Shadow, tokenName: string): SingleBoxShadowToken => {
|
||||
const createShadowToken = (
|
||||
shadow: Shadow,
|
||||
tokenName: string
|
||||
): SingleBoxShadowToken => {
|
||||
return {
|
||||
name: tokenName,
|
||||
type: TokenTypes.BOX_SHADOW,
|
||||
value: `${shadow.offset[0]}px ${shadow.offset[1]}px ${shadow.blur}px 0px ${shadow.color}`
|
||||
};
|
||||
};
|
||||
value: `${shadow.offset[0]}px ${shadow.offset[1]}px ${shadow.blur}px 0px ${shadow.color}`,
|
||||
}
|
||||
}
|
||||
|
||||
const popoverShadowToken = (colorScheme: ColorScheme): SingleBoxShadowToken => {
|
||||
const shadow = colorScheme.popoverShadow;
|
||||
return createShadowToken(shadow, "popoverShadow");
|
||||
};
|
||||
const shadow = colorScheme.popoverShadow
|
||||
return createShadowToken(shadow, "popoverShadow")
|
||||
}
|
||||
|
||||
const modalShadowToken = (colorScheme: ColorScheme): SingleBoxShadowToken => {
|
||||
const shadow = colorScheme.modalShadow;
|
||||
return createShadowToken(shadow, "modalShadow");
|
||||
};
|
||||
const shadow = colorScheme.modalShadow
|
||||
return createShadowToken(shadow, "modalShadow")
|
||||
}
|
||||
|
||||
type ThemeSyntaxColorTokens = Record<keyof ThemeSyntax, SingleColorToken>
|
||||
|
||||
function syntaxHighlightStyleColorTokens(syntax: Syntax): ThemeSyntaxColorTokens {
|
||||
function syntaxHighlightStyleColorTokens(
|
||||
syntax: Syntax
|
||||
): ThemeSyntaxColorTokens {
|
||||
const styleKeys = Object.keys(syntax) as (keyof Syntax)[]
|
||||
|
||||
return styleKeys.reduce((acc, styleKey) => {
|
||||
|
@ -46,13 +61,16 @@ function syntaxHighlightStyleColorTokens(syntax: Syntax): ThemeSyntaxColorTokens
|
|||
// This can happen because we have a "constructor" property on the syntax object
|
||||
// and a "constructor" property on the prototype of the syntax object
|
||||
// To work around this just assert that the type of the style is not a function
|
||||
if (!syntax[styleKey] || typeof syntax[styleKey] === 'function') return acc;
|
||||
const { color } = syntax[styleKey] as Required<SyntaxHighlightStyle>;
|
||||
return { ...acc, [styleKey]: colorToken(styleKey, color) };
|
||||
}, {} as ThemeSyntaxColorTokens);
|
||||
if (!syntax[styleKey] || typeof syntax[styleKey] === "function")
|
||||
return acc
|
||||
const { color } = syntax[styleKey] as Required<SyntaxHighlightStyle>
|
||||
return { ...acc, [styleKey]: colorToken(styleKey, color) }
|
||||
}, {} as ThemeSyntaxColorTokens)
|
||||
}
|
||||
|
||||
const syntaxTokens = (colorScheme: ColorScheme): ColorSchemeTokens['syntax'] => {
|
||||
const syntaxTokens = (
|
||||
colorScheme: ColorScheme
|
||||
): ColorSchemeTokens["syntax"] => {
|
||||
const syntax = editor(colorScheme).syntax
|
||||
|
||||
return syntaxHighlightStyleColorTokens(syntax)
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
import { SingleColorToken } from "@tokens-studio/types";
|
||||
import { Layer, Style, StyleSet } from "../colorScheme";
|
||||
import { colorToken } from "./token";
|
||||
import { SingleColorToken } from "@tokens-studio/types"
|
||||
import { Layer, Style, StyleSet } from "../colorScheme"
|
||||
import { colorToken } from "./token"
|
||||
|
||||
interface StyleToken {
|
||||
background: SingleColorToken,
|
||||
border: SingleColorToken,
|
||||
foreground: SingleColorToken,
|
||||
background: SingleColorToken
|
||||
border: SingleColorToken
|
||||
foreground: SingleColorToken
|
||||
}
|
||||
|
||||
interface StyleSetToken {
|
||||
|
@ -37,24 +37,27 @@ export const styleToken = (style: Style, name: string): StyleToken => {
|
|||
return token
|
||||
}
|
||||
|
||||
export const styleSetToken = (styleSet: StyleSet, name: string): StyleSetToken => {
|
||||
const token: StyleSetToken = {} as StyleSetToken;
|
||||
export const styleSetToken = (
|
||||
styleSet: StyleSet,
|
||||
name: string
|
||||
): StyleSetToken => {
|
||||
const token: StyleSetToken = {} as StyleSetToken
|
||||
|
||||
for (const style in styleSet) {
|
||||
const s = style as keyof StyleSet;
|
||||
token[s] = styleToken(styleSet[s], `${name}${style}`);
|
||||
const s = style as keyof StyleSet
|
||||
token[s] = styleToken(styleSet[s], `${name}${style}`)
|
||||
}
|
||||
|
||||
return token;
|
||||
return token
|
||||
}
|
||||
|
||||
export const layerToken = (layer: Layer, name: string): LayerToken => {
|
||||
const token: LayerToken = {} as LayerToken;
|
||||
const token: LayerToken = {} as LayerToken
|
||||
|
||||
for (const styleSet in layer) {
|
||||
const s = styleSet as keyof Layer;
|
||||
token[s] = styleSetToken(layer[s], `${name}${styleSet}`);
|
||||
const s = styleSet as keyof Layer
|
||||
token[s] = styleSetToken(layer[s], `${name}${styleSet}`)
|
||||
}
|
||||
|
||||
return token;
|
||||
return token
|
||||
}
|
||||
|
|
|
@ -6,13 +6,21 @@ export type PlayerToken = Record<"selection" | "cursor", SingleColorToken>
|
|||
|
||||
export type PlayersToken = Record<keyof Players, PlayerToken>
|
||||
|
||||
function buildPlayerToken(colorScheme: ColorScheme, index: number): PlayerToken {
|
||||
|
||||
function buildPlayerToken(
|
||||
colorScheme: ColorScheme,
|
||||
index: number
|
||||
): PlayerToken {
|
||||
const playerNumber = index.toString() as keyof Players
|
||||
|
||||
return {
|
||||
selection: colorToken(`player${index}Selection`, colorScheme.players[playerNumber].selection),
|
||||
cursor: colorToken(`player${index}Cursor`, colorScheme.players[playerNumber].cursor),
|
||||
selection: colorToken(
|
||||
`player${index}Selection`,
|
||||
colorScheme.players[playerNumber].selection
|
||||
),
|
||||
cursor: colorToken(
|
||||
`player${index}Cursor`,
|
||||
colorScheme.players[playerNumber].cursor
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -24,5 +32,5 @@ export const playersToken = (colorScheme: ColorScheme): PlayersToken => ({
|
|||
"4": buildPlayerToken(colorScheme, 4),
|
||||
"5": buildPlayerToken(colorScheme, 5),
|
||||
"6": buildPlayerToken(colorScheme, 6),
|
||||
"7": buildPlayerToken(colorScheme, 7)
|
||||
"7": buildPlayerToken(colorScheme, 7),
|
||||
})
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue