diff --git a/docfx/articles/dock-theme-design-tokens.md b/docfx/articles/dock-theme-design-tokens.md new file mode 100644 index 000000000..ca35128df --- /dev/null +++ b/docfx/articles/dock-theme-design-tokens.md @@ -0,0 +1,198 @@ +# Dock Theme Design Tokens + +Dock themes expose semantic design tokens that decouple control templates from raw color/size keys. + +All token keys below are available in both: + +- `DockFluentTheme` +- `DockSimpleTheme` + +## Surface tokens + +| Token | Purpose | +|---|---| +| `DockSurfaceWorkbenchBrush` | Main background surface behind docks. | +| `DockSurfaceSidebarBrush` | Sidebar/tool-area surface. | +| `DockSurfaceEditorBrush` | Document/editor surface. | +| `DockSurfacePanelBrush` | Secondary panel surface. | +| `DockSurfaceHeaderBrush` | Header/chrome background. | +| `DockSurfaceHeaderActiveBrush` | Active header/chrome background. | + +## Border and structure tokens + +| Token | Purpose | +|---|---| +| `DockBorderSubtleBrush` | Standard low-emphasis border color. | +| `DockBorderStrongBrush` | Stronger border color (optional use). | +| `DockSeparatorBrush` | Thin separators and strip dividers. | +| `DockSplitterIdleBrush` | Splitter idle color. | +| `DockSplitterHoverBrush` | Splitter hover color. | +| `DockSplitterDragBrush` | Splitter drag color. | +| `DockTargetIndicatorBrush` | Dock target overlay indicator color. | + +## Tab tokens + +| Token | Purpose | +|---|---| +| `DockTabBackgroundBrush` | Base tab strip background. | +| `DockTabHoverBackgroundBrush` | Tab hover background. | +| `DockTabActiveBackgroundBrush` | Active/selected tab background. | +| `DockTabActiveIndicatorBrush` | Active tab indicator line/accent. | +| `DockTabForegroundBrush` | Default tab text/icon color. | +| `DockTabSelectedForegroundBrush` | Selected tab text color (non-active accent). | +| `DockTabActiveForegroundBrush` | Active tab text/icon color. | +| `DockTabCloseHoverBackgroundBrush` | Document close button hover background. | + +## Chrome button tokens + +| Token | Purpose | +|---|---| +| `DockChromeButtonForegroundBrush` | Tool/MDI chrome icon color. | +| `DockChromeButtonHoverBackgroundBrush` | Chrome button hover background. | +| `DockChromeButtonPressedBackgroundBrush` | Chrome button pressed background. | +| `DockChromeButtonDangerHoverBrush` | Close/danger button hover background. | + +## Global shape/density tokens + +| Token | Purpose | +|---|---| +| `DockCornerRadiusSmall` | Shared small corner radius. | +| `DockHeaderHeight` | Header/chrome nominal height token. | +| `DockTabHeight` | Tab height token. | +| `DockTabHorizontalPadding` | Horizontal tab spacing token. | +| `DockIconSizeSmall` | Small icon size token. | +| `DockIconSizeNormal` | Normal icon size token. | + +## Control density tokens + +| Token | Purpose | +|---|---| +| `DockTabItemMinHeight` | Min height for document/tool tab items. | +| `DockToolTabItemMinHeight` | Min height for tool tab items. | +| `DockDocumentTabItemPadding` | Document tab item padding. | +| `DockToolTabItemPadding` | Tool tab item padding. | +| `DockToolTabItemSelectedPadding` | Tool tab selected-state padding. | +| `DockCreateButtonWidth` | Document create button width. | +| `DockCreateButtonHeight` | Document create button height. | +| `DockCloseButtonSize` | Document close button size. | +| `DockChromeButtonWidth` | Tool/MDI chrome button width. | +| `DockChromeButtonHeight` | Tool/MDI chrome button height. | +| `DockChromeButtonPadding` | Tool/MDI chrome button padding. | +| `DockChromeButtonMargin` | Tool/MDI chrome button margin. | +| `DockMdiTitleIconSize` | MDI title bar icon viewbox size. | +| `DockMdiHeaderDragPadding` | MDI drag header padding. | + +## Tab and header content metrics + +| Token | Purpose | +|---|---| +| `DockHeaderContentPadding` | Shared title/header text padding in document/tool/MDI headers. | +| `DockModifiedIndicatorMargin` | Shared margin for modified marker (`*`). | +| `DockTabContentSpacing` | Spacing between tab content blocks (icon, title, modified, close). | +| `DockTabContentMargin` | Inner margin around tab content stack. | +| `DockCreateButtonIconMargin` | Plus/create icon margin in document tab strip. | + +## Tool chrome and MDI metrics + +| Token | Purpose | +|---|---| +| `DockToolChromeHeaderMargin` | Header dock panel outer margin in tool chrome. | +| `DockToolChromeTitleMargin` | Tool title text margin. | +| `DockToolChromeMenuIconMargin` | Menu icon margin in tool chrome button. | +| `DockToolChromeDividerThickness` | Divider line thickness below tool chrome header. | +| `DockChromeGripHeight` | Drag grip strip height in tool/MDI chrome. | +| `DockChromeGripMargin` | Drag grip strip margin in tool/MDI chrome. | +| `DockChromeGripBrush` | Shared tiled grip brush used by tool/MDI chrome. | +| `DockMdiHeaderColumnSpacing` | Column spacing in MDI document header layout. | +| `DockMdiButtonStripSpacing` | Spacing between MDI title bar buttons. | +| `DockMdiButtonStripMargin` | MDI button strip margin. | +| `DockMdiResizeEdgeThickness` | Hit target thickness for MDI edge resize handles. | +| `DockMdiResizeCornerSize` | Hit target size for MDI corner resize handles. | + +## Selector and target metrics + +| Token | Purpose | +|---|---| +| `DockTargetSelectorSize` | Dock target selector image size. | +| `DockTargetSelectorGridMaxSize` | Max size of local dock target selector grid. | +| `DockSelectorOverlayBackdropBrush` | Backdrop brush for selector overlay modal layer. | +| `DockSelectorOverlayCornerRadius` | Corner radius for selector overlay container. | +| `DockSelectorOverlayPadding` | Selector overlay container padding. | +| `DockSelectorOverlayMinWidth` | Selector overlay minimum width. | +| `DockSelectorOverlayMaxWidth` | Selector overlay maximum width. | +| `DockSelectorOverlaySpacing` | Vertical spacing in selector overlay content. | +| `DockSelectorOverlayListMinHeight` | Minimum list height in selector overlay. | +| `DockSelectorOverlayListMaxHeight` | Maximum list height in selector overlay. | +| `DockSelectorOverlayItemPadding` | List item padding in selector overlay. | +| `DockSelectorOverlayItemCornerRadius` | List item corner radius in selector overlay. | +| `DockSelectorOverlayBadgeSpacing` | Spacing between state badges. | +| `DockSelectorOverlayBadgeMargin` | Badge strip margin. | +| `DockSelectorOverlayBadgeCornerRadius` | Badge corner radius. | +| `DockSelectorOverlayBadgePadding` | Badge padding. | +| `DockSelectorOverlayBadgeFontSize` | Badge text size. | + +## Command bar and drag preview metrics + +| Token | Purpose | +|---|---| +| `DockCommandBarPadding` | Command bar container padding. | +| `DockCommandBarSpacing` | Vertical spacing between menu/tool/ribbon bands. | +| `DockDragPreviewCornerRadius` | Drag preview card corner radius. | +| `DockDragPreviewHeaderPadding` | Drag preview header padding. | +| `DockDragPreviewHeaderSpacing` | Header spacing between title and status block. | +| `DockDragPreviewStatusSpacing` | Spacing between drag status icon and text. | +| `DockDragPreviewStatusIconSize` | Drag status icon size. | +| `DockHostTitleBarMouseTrackerHeight` | Host title bar top tracker panel height. | + +## Overlay and dialog metrics + +| Token | Purpose | +|---|---| +| `DockOverlayReloadButtonMargin` | Margin for the reload command button in busy overlay. | +| `DockOverlayCardCornerRadius` | Busy overlay card corner radius. | +| `DockOverlayCardPadding` | Busy overlay card padding. | +| `DockOverlayCardSpacing` | Busy overlay card content spacing. | +| `DockOverlayMessageFontSize` | Shared overlay message/title font size. | +| `DockOverlayProgressWidth` | Busy overlay progress width. | +| `DockOverlayProgressHeight` | Busy overlay progress height. | +| `DockDialogCornerRadius` | Dialog shell corner radius. | +| `DockDialogPadding` | Dialog shell padding. | +| `DockDialogMinWidth` | Dialog shell minimum width. | +| `DockDialogMaxWidth` | Dialog shell maximum width. | +| `DockDialogSpacing` | Dialog shell grid row/column spacing. | +| `DockDialogTitleFontSize` | Dialog and confirmation title font size. | +| `DockDialogCloseButtonSize` | Dialog shell close button size. | +| `DockConfirmationDialogPadding` | Confirmation dialog padding. | +| `DockConfirmationDialogMaxWidth` | Confirmation dialog maximum width. | +| `DockConfirmationDialogStackSpacing` | Confirmation dialog content spacing. | +| `DockConfirmationDialogActionsSpacing` | Confirmation dialog actions row spacing. | + +## Where defaults are defined + +- Fluent defaults: `src/Dock.Avalonia.Themes.Fluent/Accents/Fluent.axaml` +- Simple defaults: `src/Dock.Avalonia.Themes.Simple/Accents/Simple.axaml` +- Compact density overrides: + - `src/Dock.Avalonia.Themes.Fluent/DensityStyles/Compact.axaml` + - `src/Dock.Avalonia.Themes.Simple/DensityStyles/Compact.axaml` + +## Token override pattern + +```xaml + + + + + + + 22 + + + + +``` + +## Related docs + +- [Dock Fluent Theme](dock-theme-fluent.md) +- [Dock Simple Theme](dock-theme-simple.md) +- [Dock Theme Token Migration](dock-theme-token-migration.md) diff --git a/docfx/articles/dock-theme-fluent.md b/docfx/articles/dock-theme-fluent.md new file mode 100644 index 000000000..e091091ee --- /dev/null +++ b/docfx/articles/dock-theme-fluent.md @@ -0,0 +1,114 @@ +# Dock Fluent Theme + +This guide documents the Dock Fluent theme in detail: structure, density modes, token customization, and IDE preset integration. + +## Overview + +`DockFluentTheme` is the primary Dock theme for Fluent-based applications. + +```xaml + + + + +``` + +Theme implementation: + +- `src/Dock.Avalonia.Themes.Fluent/DockFluentTheme.axaml` +- `src/Dock.Avalonia.Themes.Fluent/DockFluentTheme.axaml.cs` +- `src/Dock.Avalonia.Themes.Fluent/Accents/Fluent.axaml` + +## What DockFluentTheme includes + +`DockFluentTheme` merges: + +- accent and string resources, +- window chrome resources, +- all Dock control templates (documents, tools, MDI, overlays, host windows). + +Key control template resources include: + +- `DocumentControl.axaml` +- `DocumentTabStrip.axaml` +- `DocumentTabStripItem.axaml` +- `ToolChromeControl.axaml` +- `ToolTabStrip.axaml` +- `ToolTabStripItem.axaml` +- `MdiDocumentWindow.axaml` + +## Density support + +`DockFluentTheme` supports two density modes: + +- `DensityStyle="Normal"` (default), +- `DensityStyle="Compact"`. + +```xaml + +``` + +Compact density resource dictionary: + +- `avares://Dock.Avalonia.Themes.Fluent/DensityStyles/Compact.axaml` + +Compact mode reduces tab/button/icon metrics by overriding density tokens (for example `DockTabItemMinHeight`, `DockCloseButtonSize`, `DockChromeButtonWidth`). + +## Fluent token customization + +Customize Dock Fluent visuals by overriding semantic tokens after `DockFluentTheme`. + +```xaml + + + + + + + + + + + +``` + +For the complete token list, see [Dock Theme Design Tokens](dock-theme-design-tokens.md). + +## IDE preset usage with Fluent + +Fluent-specific preset dictionaries: + +- `avares://Dock.Avalonia.Themes.Fluent/Presets/Ide/Default.axaml` +- `avares://Dock.Avalonia.Themes.Fluent/Presets/Ide/VsCodeDark.axaml` +- `avares://Dock.Avalonia.Themes.Fluent/Presets/Ide/VsCodeLight.axaml` +- `avares://Dock.Avalonia.Themes.Fluent/Presets/Ide/RiderLight.axaml` +- `avares://Dock.Avalonia.Themes.Fluent/Presets/Ide/RiderDark.axaml` + +```xaml + + + + + + + + + + + +``` + +Presets override color/surface tokens only; density remains controlled by `DensityStyle`. + +## Recommended customization order + +1. Apply `DockFluentTheme`. +2. Pick density (`Normal` or `Compact`). +3. Optionally merge an IDE preset. +4. Add app-specific token overrides last. + +## Related docs + +- [Dock Theme Design Tokens](dock-theme-design-tokens.md) +- [Dock IDE Presets](dock-theme-ide-presets.md) +- [Dock Theme Token Migration](dock-theme-token-migration.md) diff --git a/docfx/articles/dock-theme-ide-presets.md b/docfx/articles/dock-theme-ide-presets.md new file mode 100644 index 000000000..5525a6026 --- /dev/null +++ b/docfx/articles/dock-theme-ide-presets.md @@ -0,0 +1,164 @@ +# Dock IDE Presets + +Dock ships token-only IDE presets for Fluent and Simple themes. +They override semantic tokens only and do not replace Dock control templates. + +## Preset files + +Fluent: +- `avares://Dock.Avalonia.Themes.Fluent/Presets/Ide/Default.axaml` +- `avares://Dock.Avalonia.Themes.Fluent/Presets/Ide/VsCodeDark.axaml` +- `avares://Dock.Avalonia.Themes.Fluent/Presets/Ide/VsCodeLight.axaml` +- `avares://Dock.Avalonia.Themes.Fluent/Presets/Ide/RiderLight.axaml` +- `avares://Dock.Avalonia.Themes.Fluent/Presets/Ide/RiderDark.axaml` + +Simple: +- `avares://Dock.Avalonia.Themes.Simple/Presets/Ide/Default.axaml` +- `avares://Dock.Avalonia.Themes.Simple/Presets/Ide/VsCodeDark.axaml` +- `avares://Dock.Avalonia.Themes.Simple/Presets/Ide/VsCodeLight.axaml` +- `avares://Dock.Avalonia.Themes.Simple/Presets/Ide/RiderLight.axaml` +- `avares://Dock.Avalonia.Themes.Simple/Presets/Ide/RiderDark.axaml` + +## Usage + +### Fluent + Default + +```xaml + + + + + + + + + + + +``` + +### Fluent + VS Code Dark + +```xaml + + + + + + + + + + + +``` + +### Fluent + Rider Light + +```xaml + + + + + + + + + + + +``` + +### Fluent + VS Code Light + +```xaml + + + + + + + + + + + +``` + +### Fluent + Rider Dark + +```xaml + + + + + + + + + + + +``` + +### Simple + Default + +```xaml + + + + + + + + + + + +``` + +### Simple + VS Code Dark + +```xaml + + + + + + + + + + + +``` + +### Simple + Rider Dark + +```xaml + + + + + + + + + + + +``` + +## Customization workflow + +1. Start from one preset. +2. Copy it into your app. +3. Adjust only token values you need. +4. Keep your dictionary after the Dock theme include so overrides win. + +For the full token list, see [Dock Theme Semantic Tokens](dock-theme-semantic-tokens.md). + +IDE presets intentionally override color/surface tokens only. +Density continues to come from `DockFluentTheme.DensityStyle` and `DockSimpleTheme.DensityStyle`. + +Use `Default.axaml` when you want preset-driven runtime switching but need to return to the base Dock theme token mapping. diff --git a/docfx/articles/dock-theme-semantic-tokens.md b/docfx/articles/dock-theme-semantic-tokens.md new file mode 100644 index 000000000..739b6230c --- /dev/null +++ b/docfx/articles/dock-theme-semantic-tokens.md @@ -0,0 +1,63 @@ +# Dock Theme Semantic Tokens + +Dock now exposes a semantic token contract on top of existing Fluent/Simple resources. + +Use these tokens to style Dock surfaces/chrome without copying control templates. + +For the full detailed reference, see [Dock Theme Design Tokens](dock-theme-design-tokens.md). +For migration from legacy keys, see [Dock Theme Token Migration](dock-theme-token-migration.md). + +## Token contract + +Both built-in themes expose the same keys: + +| Group | Keys | +|---|---| +| Surfaces | `DockSurfaceWorkbenchBrush`, `DockSurfaceSidebarBrush`, `DockSurfaceEditorBrush`, `DockSurfacePanelBrush`, `DockSurfaceHeaderBrush`, `DockSurfaceHeaderActiveBrush` | +| Borders/structure | `DockBorderSubtleBrush`, `DockBorderStrongBrush`, `DockSeparatorBrush`, `DockSplitterIdleBrush`, `DockSplitterHoverBrush`, `DockSplitterDragBrush`, `DockTargetIndicatorBrush` | +| Tabs | `DockTabBackgroundBrush`, `DockTabHoverBackgroundBrush`, `DockTabActiveBackgroundBrush`, `DockTabActiveIndicatorBrush`, `DockTabForegroundBrush`, `DockTabSelectedForegroundBrush`, `DockTabActiveForegroundBrush`, `DockTabCloseHoverBackgroundBrush` | +| Chrome buttons | `DockChromeButtonForegroundBrush`, `DockChromeButtonHoverBackgroundBrush`, `DockChromeButtonPressedBackgroundBrush`, `DockChromeButtonDangerHoverBrush` | +| Density/shape | `DockCornerRadiusSmall`, `DockHeaderHeight`, `DockTabHeight`, `DockTabHorizontalPadding`, `DockIconSizeSmall`, `DockIconSizeNormal` | +| Density controls | `DockTabItemMinHeight`, `DockToolTabItemMinHeight`, `DockDocumentTabItemPadding`, `DockToolTabItemPadding`, `DockToolTabItemSelectedPadding`, `DockCreateButtonWidth`, `DockCreateButtonHeight`, `DockCloseButtonSize`, `DockChromeButtonWidth`, `DockChromeButtonHeight`, `DockChromeButtonPadding`, `DockChromeButtonMargin`, `DockMdiTitleIconSize`, `DockMdiHeaderDragPadding` | +| Tab/header metrics | `DockHeaderContentPadding`, `DockModifiedIndicatorMargin`, `DockTabContentSpacing`, `DockTabContentMargin`, `DockCreateButtonIconMargin` | +| Chrome/MDI metrics | `DockToolChromeHeaderMargin`, `DockToolChromeTitleMargin`, `DockToolChromeMenuIconMargin`, `DockToolChromeDividerThickness`, `DockChromeGripHeight`, `DockChromeGripMargin`, `DockChromeGripBrush`, `DockMdiHeaderColumnSpacing`, `DockMdiButtonStripSpacing`, `DockMdiButtonStripMargin`, `DockMdiResizeEdgeThickness`, `DockMdiResizeCornerSize` | +| Selector/target metrics | `DockTargetSelectorSize`, `DockTargetSelectorGridMaxSize`, `DockSelectorOverlayBackdropBrush`, `DockSelectorOverlayCornerRadius`, `DockSelectorOverlayPadding`, `DockSelectorOverlayMinWidth`, `DockSelectorOverlayMaxWidth`, `DockSelectorOverlaySpacing`, `DockSelectorOverlayListMinHeight`, `DockSelectorOverlayListMaxHeight`, `DockSelectorOverlayItemPadding`, `DockSelectorOverlayItemCornerRadius`, `DockSelectorOverlayBadgeSpacing`, `DockSelectorOverlayBadgeMargin`, `DockSelectorOverlayBadgeCornerRadius`, `DockSelectorOverlayBadgePadding`, `DockSelectorOverlayBadgeFontSize` | +| Command/preview metrics | `DockCommandBarPadding`, `DockCommandBarSpacing`, `DockDragPreviewCornerRadius`, `DockDragPreviewHeaderPadding`, `DockDragPreviewHeaderSpacing`, `DockDragPreviewStatusSpacing`, `DockDragPreviewStatusIconSize`, `DockHostTitleBarMouseTrackerHeight` | +| Overlay/dialog metrics | `DockOverlayReloadButtonMargin`, `DockOverlayCardCornerRadius`, `DockOverlayCardPadding`, `DockOverlayCardSpacing`, `DockOverlayMessageFontSize`, `DockOverlayProgressWidth`, `DockOverlayProgressHeight`, `DockDialogCornerRadius`, `DockDialogPadding`, `DockDialogMinWidth`, `DockDialogMaxWidth`, `DockDialogSpacing`, `DockDialogTitleFontSize`, `DockDialogCloseButtonSize`, `DockConfirmationDialogPadding`, `DockConfirmationDialogMaxWidth`, `DockConfirmationDialogStackSpacing`, `DockConfirmationDialogActionsSpacing` | + +## Default mapping + +Defaults are defined in: + +- `src/Dock.Avalonia.Themes.Fluent/Accents/Fluent.axaml` +- `src/Dock.Avalonia.Themes.Simple/Accents/Simple.axaml` + +These defaults map to existing Dock theme resources so current apps keep their prior look unless you override tokens. + +## Override example + +```xaml + + + + + 24 + +``` + +Merge this dictionary after `DockFluentTheme` or `DockSimpleTheme`. + +## Compact density mode + +Both Dock theme classes expose a `DensityStyle` property: + +```xaml + + +``` + +Compact mode loads: + +- `avares://Dock.Avalonia.Themes.Fluent/DensityStyles/Compact.axaml` +- `avares://Dock.Avalonia.Themes.Simple/DensityStyles/Compact.axaml` diff --git a/docfx/articles/dock-theme-simple.md b/docfx/articles/dock-theme-simple.md new file mode 100644 index 000000000..b5ae9a5c2 --- /dev/null +++ b/docfx/articles/dock-theme-simple.md @@ -0,0 +1,106 @@ +# Dock Simple Theme + +This guide documents the Dock Simple theme in detail, including architecture, density support, and token/preset customization. + +## Overview + +`DockSimpleTheme` is the Dock theme for applications using Avalonia `SimpleTheme`. + +```xaml + + + + +``` + +Theme implementation: + +- `src/Dock.Avalonia.Themes.Simple/DockSimpleTheme.axaml` +- `src/Dock.Avalonia.Themes.Simple/DockSimpleTheme.axaml.cs` +- `src/Dock.Avalonia.Themes.Simple/Accents/Simple.axaml` + +## Architecture notes + +Dock Simple uses its own accent resources but shares Dock control template XAML with Fluent. + +This is implemented by linking Fluent control `.axaml` files into the Simple theme assembly: + +- `src/Dock.Avalonia.Themes.Simple/Dock.Avalonia.Themes.Simple.csproj` + +Practical result: + +- same control template behavior, +- same semantic token contract, +- different default accent mappings. + +## Density support + +`DockSimpleTheme` supports: + +- `DensityStyle="Normal"` (default), +- `DensityStyle="Compact"`. + +```xaml + +``` + +Compact density resource dictionary: + +- `avares://Dock.Avalonia.Themes.Simple/DensityStyles/Compact.axaml` + +## Simple token customization + +Override semantic tokens after `DockSimpleTheme`: + +```xaml + + + + + + + + + + + +``` + +For the complete token list, see [Dock Theme Design Tokens](dock-theme-design-tokens.md). + +## IDE preset usage with Simple + +Simple-specific preset dictionaries: + +- `avares://Dock.Avalonia.Themes.Simple/Presets/Ide/Default.axaml` +- `avares://Dock.Avalonia.Themes.Simple/Presets/Ide/VsCodeDark.axaml` +- `avares://Dock.Avalonia.Themes.Simple/Presets/Ide/VsCodeLight.axaml` +- `avares://Dock.Avalonia.Themes.Simple/Presets/Ide/RiderLight.axaml` +- `avares://Dock.Avalonia.Themes.Simple/Presets/Ide/RiderDark.axaml` + +```xaml + + + + + + + + + + + +``` + +## Recommended customization order + +1. Apply `DockSimpleTheme`. +2. Choose density. +3. Optionally merge an IDE preset. +4. Add app-specific token overrides at highest priority. + +## Related docs + +- [Dock Theme Design Tokens](dock-theme-design-tokens.md) +- [Dock IDE Presets](dock-theme-ide-presets.md) +- [Dock Theme Token Migration](dock-theme-token-migration.md) diff --git a/docfx/articles/dock-theme-token-migration.md b/docfx/articles/dock-theme-token-migration.md new file mode 100644 index 000000000..62d06c1fa --- /dev/null +++ b/docfx/articles/dock-theme-token-migration.md @@ -0,0 +1,117 @@ +# Dock Theme Token Migration + +This guide helps migrate from older Dock token usage (legacy theme keys and hardcoded metrics) to the current semantic token model. + +## Why migrate + +Semantic tokens provide: + +- stable customization points across Fluent and Simple, +- better compatibility with IDE presets, +- density support (`Normal`/`Compact`) without copying templates. + +## Migration strategy + +1. Keep existing themes (`DockFluentTheme` or `DockSimpleTheme`). +2. Move custom overrides from legacy keys to semantic keys. +3. Replace hardcoded metric overrides with density tokens. +4. Use `DensityStyle="Compact"` when compact layout is needed. + +## Legacy to semantic token mapping + +| Legacy key | Replace with | Notes | +|---|---|---| +| `DockThemeBackgroundBrush` | `DockSurfaceWorkbenchBrush`, `DockSurfaceSidebarBrush`, `DockSurfacePanelBrush`, `DockTabActiveBackgroundBrush` | Split by usage intent. | +| `DockThemeControlBackgroundBrush` | `DockSurfaceEditorBrush`, `DockTabHoverBackgroundBrush`, `DockChromeButtonHoverBackgroundBrush` | Separate editor and hover/chrome concerns. | +| `DockThemeBorderLowBrush` | `DockBorderSubtleBrush`, `DockSeparatorBrush`, `DockSplitterIdleBrush` | Use `DockBorderStrongBrush` for stronger borders when needed. | +| `DockThemeAccentBrush` | `DockSurfaceHeaderActiveBrush` | Active header/chrome background token. | +| `DockThemeForegroundBrush` | `DockTabForegroundBrush` | Base tab/chrome foreground role. | +| `DockApplicationAccentBrushLow` | `DockTabActiveBackgroundBrush`, `DockTabActiveIndicatorBrush`, `DockSplitterHoverBrush` | Token choice depends on template area. | +| `DockApplicationAccentBrushMed` | `DockTabHoverBackgroundBrush`, `DockTabSelectedForegroundBrush`, `DockSplitterDragBrush` | Separate hover, selected text, and drag indicators. | +| `DockApplicationAccentBrushHigh` | `DockTabCloseHoverBackgroundBrush`, `DockChromeButtonDangerHoverBrush` | Close/danger hover states. | +| `DockApplicationAccentForegroundBrush` | `DockTabActiveForegroundBrush` | Active text/icon foreground. | +| `DockApplicationAccentBrushIndicator` | `DockTargetIndicatorBrush` | Dock-target overlay indicator. | +| `DockToolChromeIconBrush` | `DockChromeButtonForegroundBrush` | Tool/MDI chrome icon foreground. | + +## Hardcoded metric migration + +Older template customizations often hardcoded values like `Width="14"`, `Height="24"`, `Padding="4,0,4,0"`. + +Use tokens instead: + +| Hardcoded usage | Semantic metric token | +|---|---| +| Document close button size | `DockCloseButtonSize` | +| Create document button size | `DockCreateButtonWidth`, `DockCreateButtonHeight` | +| Document tab min height | `DockTabItemMinHeight` | +| Tool tab min height | `DockToolTabItemMinHeight` | +| Document tab padding | `DockDocumentTabItemPadding` | +| Tool tab padding | `DockToolTabItemPadding`, `DockToolTabItemSelectedPadding` | +| Tool/MDI chrome button size | `DockChromeButtonWidth`, `DockChromeButtonHeight` | +| Tool/MDI chrome button spacing | `DockChromeButtonPadding`, `DockChromeButtonMargin` | +| MDI title icon sizing | `DockMdiTitleIconSize` | +| MDI drag header padding | `DockMdiHeaderDragPadding` | +| Header text padding | `DockHeaderContentPadding` | +| Modified marker spacing | `DockModifiedIndicatorMargin` | +| Tab content spacing/margins | `DockTabContentSpacing`, `DockTabContentMargin` | +| Create icon margin | `DockCreateButtonIconMargin` | +| Tool chrome title/header spacing | `DockToolChromeHeaderMargin`, `DockToolChromeTitleMargin`, `DockToolChromeMenuIconMargin` | +| Tool divider thickness | `DockToolChromeDividerThickness` | +| Shared chrome grip visuals | `DockChromeGripHeight`, `DockChromeGripMargin`, `DockChromeGripBrush` | +| MDI header/buttons layout spacing | `DockMdiHeaderColumnSpacing`, `DockMdiButtonStripSpacing`, `DockMdiButtonStripMargin` | +| MDI resize handle hit targets | `DockMdiResizeEdgeThickness`, `DockMdiResizeCornerSize` | +| Dock target selector sizing | `DockTargetSelectorSize`, `DockTargetSelectorGridMaxSize` | +| Selector overlay sizing/spacing/badges | `DockSelectorOverlay*` token family | +| Command bar layout spacing | `DockCommandBarPadding`, `DockCommandBarSpacing` | +| Drag preview layout sizing | `DockDragPreviewCornerRadius`, `DockDragPreviewHeaderPadding`, `DockDragPreviewHeaderSpacing`, `DockDragPreviewStatusSpacing`, `DockDragPreviewStatusIconSize` | +| Host title tracker strip height | `DockHostTitleBarMouseTrackerHeight` | +| Busy overlay card and progress sizing | `DockOverlayReloadButtonMargin`, `DockOverlayCardCornerRadius`, `DockOverlayCardPadding`, `DockOverlayCardSpacing`, `DockOverlayMessageFontSize`, `DockOverlayProgressWidth`, `DockOverlayProgressHeight` | +| Dialog shell sizing | `DockDialogCornerRadius`, `DockDialogPadding`, `DockDialogMinWidth`, `DockDialogMaxWidth`, `DockDialogSpacing`, `DockDialogTitleFontSize`, `DockDialogCloseButtonSize` | +| Confirmation dialog sizing | `DockConfirmationDialogPadding`, `DockConfirmationDialogMaxWidth`, `DockConfirmationDialogStackSpacing`, `DockConfirmationDialogActionsSpacing` | + +## Example migration snippet + +Before: + +```xaml + + +``` + +After: + +```xaml + + + + +``` + +## Density migration + +Before: + +```xaml + +``` + +After: + +```xaml + +``` + +Use token overrides only for exceptions; prefer density presets for global compact/normal scaling. + +## Validation checklist + +1. Active tab indicator, hover, and selected states still look correct. +2. Tool chrome buttons keep expected hover/close behavior. +3. Dock selector/target overlays retain intended indicator contrast. +4. Compact mode updates tab and chrome sizes consistently. + +## Related docs + +- [Dock Theme Design Tokens](dock-theme-design-tokens.md) +- [Dock Fluent Theme](dock-theme-fluent.md) +- [Dock Simple Theme](dock-theme-simple.md) diff --git a/docfx/articles/toc.yml b/docfx/articles/toc.yml index 08eb576d2..975056eec 100644 --- a/docfx/articles/toc.yml +++ b/docfx/articles/toc.yml @@ -118,8 +118,20 @@ href: dock-datatemplates.md - name: Styling and theming href: dock-styling.md + - name: Fluent theme guide + href: dock-theme-fluent.md + - name: Simple theme guide + href: dock-theme-simple.md + - name: Design tokens + href: dock-theme-design-tokens.md + - name: Token migration + href: dock-theme-token-migration.md - name: Custom themes href: dock-custom-theme.md + - name: Semantic theme tokens + href: dock-theme-semantic-tokens.md + - name: IDE theme presets + href: dock-theme-ide-presets.md - name: Context menus href: dock-context-menus.md - name: Command bars diff --git a/samples/DockReactiveUIRiderSample/App.axaml b/samples/DockReactiveUIRiderSample/App.axaml index 498c91773..55e61486d 100644 --- a/samples/DockReactiveUIRiderSample/App.axaml +++ b/samples/DockReactiveUIRiderSample/App.axaml @@ -17,31 +17,98 @@ - #1E1F22 - #2B2D30 - #313335 - #3C3F41 - #4C8BF5 - #D7D7D7 - #9AA1A9 + + + + + + #FFF3F3F3 + #FFEFEFEF + #FFE7E7E7 + #FFD0D0D0 + #FF4A8BFF + #FF2C2C2C + #FF6F747A - - - - - - - + + + + + + + - #2B2D30 - #2B2D30 - #2B2D30 - #D7D7D7 - #D7D7D7 - #3C3F41 - #3C3F41 - #1E1F22 - #1E1F22 + + + + + + + + #FFF3F3F3 + #FFF3F3F3 + + + + + + #FF1E1F22 + #FF2B2D30 + #FF313338 + #FF3C3F41 + #FF4C9FFF + #FFBBBBBB + #FF9AA1A9 + + + + + + + + + + + + + + + + + #FF1E1F22 + #FF1E1F22 + + + + + + #FF1E1F22 + #FF2B2D30 + #FF313338 + #FF3C3F41 + #FF4C9FFF + #FFBBBBBB + #FF9AA1A9 + + + + + + + + + + + + + + + + + #FF1E1F22 + #FF1E1F22 + + @@ -52,13 +119,13 @@ @@ -70,11 +137,11 @@ Data="M5 2C3.895 2 3 2.895 3 4V14C3 15.105 3.895 16 5 16H11C12.105 16 13 15.105 13 14V6.414C13 6.015 12.842 5.634 12.561 5.354L9.646 2.439C9.365 2.158 8.984 2 8.586 2H5ZM4 4C4 3.448 4.448 3 5 3H8V5.5C8 6.328 8.672 7 9.5 7H12V14C12 14.552 11.552 15 11 15H5C4.448 15 4 14.552 4 14V4Z" Width="14" Height="14" - Foreground="{StaticResource RiderAccentBrush}" /> + Foreground="{DynamicResource RiderAccentBrush}" /> diff --git a/samples/DockReactiveUIRiderSample/ViewModels/MainWindowViewModel.cs b/samples/DockReactiveUIRiderSample/ViewModels/MainWindowViewModel.cs index 2daf231f7..6243eac17 100644 --- a/samples/DockReactiveUIRiderSample/ViewModels/MainWindowViewModel.cs +++ b/samples/DockReactiveUIRiderSample/ViewModels/MainWindowViewModel.cs @@ -8,6 +8,7 @@ using System.Threading.Tasks; using Avalonia; using Avalonia.Controls.ApplicationLifetimes; +using Avalonia.Styling; using Dock.Model.Controls; using Dock.Model.Core; using DockReactiveUIRiderSample.Services; @@ -28,6 +29,7 @@ public class MainWindowViewModel : ReactiveObject private string _activeDocumentStatus; private EditorDocumentViewModel? _activeDocument; private IDisposable? _caretSubscription; + private bool _isDarkTheme; public MainWindowViewModel() { @@ -61,6 +63,10 @@ public MainWindowViewModel() SaveAllCommand = ReactiveCommand.Create(SaveAllFiles); CloseSolutionCommand = ReactiveCommand.Create(CloseSolution); ExitCommand = ReactiveCommand.Create(Exit); + SwitchToLightThemeCommand = ReactiveCommand.Create(SwitchToLightTheme); + SwitchToDarkThemeCommand = ReactiveCommand.Create(SwitchToDarkTheme); + + InitializeTheme(); } public IRootDock? Layout @@ -93,6 +99,20 @@ public EditorDocumentViewModel? ActiveDocument private set => this.RaiseAndSetIfChanged(ref _activeDocument, value); } + public bool IsDarkTheme + { + get => _isDarkTheme; + private set + { + if (this.RaiseAndSetIfChanged(ref _isDarkTheme, value)) + { + this.RaisePropertyChanged(nameof(IsLightTheme)); + } + } + } + + public bool IsLightTheme => !IsDarkTheme; + public ReactiveCommand SaveFileCommand { get; } public ReactiveCommand SaveAllCommand { get; } @@ -101,6 +121,10 @@ public EditorDocumentViewModel? ActiveDocument public ReactiveCommand ExitCommand { get; } + public ReactiveCommand SwitchToLightThemeCommand { get; } + + public ReactiveCommand SwitchToDarkThemeCommand { get; } + public async Task LoadSolutionAsync(string solutionPath) { StatusText = "Loading solution..."; @@ -185,6 +209,33 @@ private void Exit() } } + private void InitializeTheme() + { + var requestedThemeVariant = Application.Current?.RequestedThemeVariant; + IsDarkTheme = requestedThemeVariant != ThemeVariant.Light; + } + + private void SwitchToLightTheme() + { + SetTheme(ThemeVariant.Light); + } + + private void SwitchToDarkTheme() + { + SetTheme(ThemeVariant.Dark); + } + + private void SetTheme(ThemeVariant themeVariant) + { + if (Application.Current is null) + { + return; + } + + Application.Current.RequestedThemeVariant = themeVariant; + IsDarkTheme = themeVariant == ThemeVariant.Dark; + } + private void SetActiveDocument(EditorDocumentViewModel? document) { _caretSubscription?.Dispose(); diff --git a/samples/DockReactiveUIRiderSample/Views/Documents/EditorDocumentView.axaml b/samples/DockReactiveUIRiderSample/Views/Documents/EditorDocumentView.axaml index 5ab533bab..8e8149c90 100644 --- a/samples/DockReactiveUIRiderSample/Views/Documents/EditorDocumentView.axaml +++ b/samples/DockReactiveUIRiderSample/Views/Documents/EditorDocumentView.axaml @@ -12,9 +12,9 @@ @@ -20,7 +20,7 @@ - + + + + + + @@ -49,8 +58,8 @@ @@ -83,8 +92,8 @@ diff --git a/samples/DockReactiveUIRiderSample/Views/MainWindow.axaml b/samples/DockReactiveUIRiderSample/Views/MainWindow.axaml index ba0a41ff6..f433608bd 100644 --- a/samples/DockReactiveUIRiderSample/Views/MainWindow.axaml +++ b/samples/DockReactiveUIRiderSample/Views/MainWindow.axaml @@ -12,10 +12,10 @@ UseLayoutRounding="True" WindowState="Normal" WindowStartupLocation="CenterScreen" - Background="{StaticResource RiderBackgroundBrush}" - Foreground="{StaticResource RiderTextBrush}" + Background="{DynamicResource RiderBackgroundBrush}" + Foreground="{DynamicResource RiderTextBrush}" BorderThickness="1" - BorderBrush="{StaticResource RiderBorderBrush}" + BorderBrush="{DynamicResource RiderBorderBrush}" FontFamily="JetBrains Sans, Segoe UI, Noto Sans, sans-serif" Title="Dock Rider Sample" Height="780" Width="1280" diff --git a/samples/DockReactiveUIRiderSample/Views/Tools/ProblemsToolView.axaml b/samples/DockReactiveUIRiderSample/Views/Tools/ProblemsToolView.axaml index 595027a75..3c69b4dbb 100644 --- a/samples/DockReactiveUIRiderSample/Views/Tools/ProblemsToolView.axaml +++ b/samples/DockReactiveUIRiderSample/Views/Tools/ProblemsToolView.axaml @@ -8,13 +8,13 @@ mc:Ignorable="d" d:DesignWidth="600" d:DesignHeight="200" x:DataType="vm:ProblemsToolViewModel" x:CompileBindings="True"> - + - + - + - + diff --git a/samples/DockReactiveUIRiderSample/Views/Tools/SolutionExplorerView.axaml b/samples/DockReactiveUIRiderSample/Views/Tools/SolutionExplorerView.axaml index e7e454a4d..104d0763d 100644 --- a/samples/DockReactiveUIRiderSample/Views/Tools/SolutionExplorerView.axaml +++ b/samples/DockReactiveUIRiderSample/Views/Tools/SolutionExplorerView.axaml @@ -12,7 +12,7 @@ - + @@ -22,7 +22,7 @@ + Foreground="{DynamicResource RiderMutedTextBrush}" /> diff --git a/samples/DockReactiveUIRiderSample/Views/Tools/StructureToolView.axaml b/samples/DockReactiveUIRiderSample/Views/Tools/StructureToolView.axaml index a8df64b51..b6ab18da9 100644 --- a/samples/DockReactiveUIRiderSample/Views/Tools/StructureToolView.axaml +++ b/samples/DockReactiveUIRiderSample/Views/Tools/StructureToolView.axaml @@ -7,7 +7,7 @@ mc:Ignorable="d" d:DesignWidth="260" d:DesignHeight="400" x:DataType="vm:StructureToolViewModel" x:CompileBindings="True"> - + diff --git a/samples/DockReactiveUIRiderSample/Views/Tools/TerminalToolView.axaml b/samples/DockReactiveUIRiderSample/Views/Tools/TerminalToolView.axaml index 93bcf7437..7569f10f8 100644 --- a/samples/DockReactiveUIRiderSample/Views/Tools/TerminalToolView.axaml +++ b/samples/DockReactiveUIRiderSample/Views/Tools/TerminalToolView.axaml @@ -7,12 +7,12 @@ mc:Ignorable="d" d:DesignWidth="600" d:DesignHeight="200" x:DataType="vm:TerminalToolViewModel" x:CompileBindings="True"> - + diff --git a/src/Dock.Avalonia.Themes.Fluent/Accents/Fluent.axaml b/src/Dock.Avalonia.Themes.Fluent/Accents/Fluent.axaml index 1c6620633..ca87461d9 100644 --- a/src/Dock.Avalonia.Themes.Fluent/Accents/Fluent.axaml +++ b/src/Dock.Avalonia.Themes.Fluent/Accents/Fluent.axaml @@ -15,8 +15,133 @@ 12 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 0 + 26 + 30 + 10 + 12 + 14 + + 24 + 0 + 4,0,4,0 + 4,1,4,0 + 4,2,4,0 + 26 + 24 + 14 + 18 + 16 + 3 + 2,0,0,0 + 12 + 6,4 + 2 + 0,0,2,0 + 2 + 2 + 5 + 8,0 + 0,4,8,4 + 2 + 1 + 5 + 0,0,2,0 + + + + + + + + + + 4 + 2 + 2,0 + 4 + 6 + 40 + 125 + + 6 + 12 + 280 + 520 + 8 + 120 + 320 + 6 + 4 + 4 + 8,0,0,0 + 3 + 4,1 + 10 + 4,2 + 4 + 4 + 4 + 4 + 2 + 10 + 1 + 16 + 8 + 24 + 8 + 14 + 200 + 6 + 10 + 20 + 360 + 640 + 12 + 16 + 28 + 24 + 480 + 12 + 8 + + M8.41687 7.57953V2.41851C8.41687 2.18743 8.22932 1.99988 7.99823 1.99988C7.76715 1.99988 7.5796 2.18743 7.5796 2.41851V7.57953H2.41863C2.18755 7.57953 2 7.76708 2 7.99816C2 8.22925 2.18755 8.41679 2.41863 8.41679H7.5796V13.5812C7.5796 13.8123 7.76715 13.9999 7.99823 13.9999C8.22932 13.9999 8.41687 13.8123 8.41687 13.5812V8.41679L13.5799 8.41851C13.811 8.41851 13.9985 8.23096 13.9985 7.99988C13.9985 7.76879 13.811 7.58125 13.5799 7.58125L8.41687 7.57953Z diff --git a/src/Dock.Avalonia.Themes.Fluent/Controls/BusyOverlayControl.axaml b/src/Dock.Avalonia.Themes.Fluent/Controls/BusyOverlayControl.axaml index a75f7bba8..e82cf7071 100644 --- a/src/Dock.Avalonia.Themes.Fluent/Controls/BusyOverlayControl.axaml +++ b/src/Dock.Avalonia.Themes.Fluent/Controls/BusyOverlayControl.axaml @@ -14,30 +14,30 @@ IsVisible="{TemplateBinding IsReloadVisible}" HorizontalAlignment="Right" VerticalAlignment="Bottom" - Margin="16" + Margin="{DynamicResource DockOverlayReloadButtonMargin}" ZIndex="31" /> - - - + diff --git a/src/Dock.Avalonia.Themes.Fluent/Controls/ConfirmationDialogControl.axaml b/src/Dock.Avalonia.Themes.Fluent/Controls/ConfirmationDialogControl.axaml index 9dfd94a6b..7088821b8 100644 --- a/src/Dock.Avalonia.Themes.Fluent/Controls/ConfirmationDialogControl.axaml +++ b/src/Dock.Avalonia.Themes.Fluent/Controls/ConfirmationDialogControl.axaml @@ -3,25 +3,25 @@ - - + + Foreground="{DynamicResource DockTabForegroundBrush}" /> + Spacing="{DynamicResource DockConfirmationDialogActionsSpacing}"> @@ -355,15 +355,15 @@ @@ -225,7 +231,7 @@ @@ -267,17 +273,17 @@ - + + + diff --git a/src/Dock.Avalonia.Themes.Fluent/Controls/ToolControl.axaml b/src/Dock.Avalonia.Themes.Fluent/Controls/ToolControl.axaml index 26af41d95..d6060b48a 100644 --- a/src/Dock.Avalonia.Themes.Fluent/Controls/ToolControl.axaml +++ b/src/Dock.Avalonia.Themes.Fluent/Controls/ToolControl.axaml @@ -26,7 +26,7 @@ - + @@ -34,7 +34,7 @@ + Margin="{DynamicResource DockModifiedIndicatorMargin}" /> @@ -73,9 +73,9 @@ diff --git a/src/Dock.Avalonia.Themes.Fluent/Controls/ToolTabStrip.axaml b/src/Dock.Avalonia.Themes.Fluent/Controls/ToolTabStrip.axaml index 7951160e3..efa5ef067 100644 --- a/src/Dock.Avalonia.Themes.Fluent/Controls/ToolTabStrip.axaml +++ b/src/Dock.Avalonia.Themes.Fluent/Controls/ToolTabStrip.axaml @@ -19,7 +19,7 @@ - + @@ -85,13 +85,13 @@ diff --git a/src/Dock.Avalonia.Themes.Fluent/Controls/ToolTabStripItem.axaml b/src/Dock.Avalonia.Themes.Fluent/Controls/ToolTabStripItem.axaml index 6d849d79a..699d8ae5c 100644 --- a/src/Dock.Avalonia.Themes.Fluent/Controls/ToolTabStripItem.axaml +++ b/src/Dock.Avalonia.Themes.Fluent/Controls/ToolTabStripItem.axaml @@ -147,14 +147,14 @@ - + - - + + - + @@ -178,8 +178,8 @@ - + Spacing="{DynamicResource DockTabContentSpacing}"> + diff --git a/src/Dock.Avalonia.Themes.Fluent/Controls/WindowChromeResources.axaml b/src/Dock.Avalonia.Themes.Fluent/Controls/WindowChromeResources.axaml index 9a568a549..804d66fb0 100644 --- a/src/Dock.Avalonia.Themes.Fluent/Controls/WindowChromeResources.axaml +++ b/src/Dock.Avalonia.Themes.Fluent/Controls/WindowChromeResources.axaml @@ -1,7 +1,7 @@ - - - - + + + + diff --git a/src/Dock.Avalonia.Themes.Fluent/DensityStyles/Compact.axaml b/src/Dock.Avalonia.Themes.Fluent/DensityStyles/Compact.axaml new file mode 100644 index 000000000..4b4a55fb5 --- /dev/null +++ b/src/Dock.Avalonia.Themes.Fluent/DensityStyles/Compact.axaml @@ -0,0 +1,61 @@ + + 22 + 22 + 8 + 10 + 12 + + 22 + 0 + 3,0,3,0 + 3,0,3,0 + 3,1,3,0 + + 24 + 22 + 12 + + 16 + 14 + 2 + 1,0,0,0 + + 10 + 4,2 + 1 + 0,0,1,0 + 1 + 1 + 4 + 6,0 + 0,3,6,3 + 1 + 4 + 0,0,1,0 + 3 + 1 + 1,0 + 3 + 3 + 3 + 1 + 9 + 12 + 20 + 6 + 13 + 180 + 5 + 16 + 320 + 560 + 10 + 14 + 24 + 20 + 440 + 10 + 6 + diff --git a/src/Dock.Avalonia.Themes.Fluent/DockFluentTheme.axaml b/src/Dock.Avalonia.Themes.Fluent/DockFluentTheme.axaml index 6154426ff..0f6ea2b22 100644 --- a/src/Dock.Avalonia.Themes.Fluent/DockFluentTheme.axaml +++ b/src/Dock.Avalonia.Themes.Fluent/DockFluentTheme.axaml @@ -55,6 +55,9 @@ + + + diff --git a/src/Dock.Avalonia.Themes.Fluent/DockFluentTheme.axaml.cs b/src/Dock.Avalonia.Themes.Fluent/DockFluentTheme.axaml.cs index d15fddbe3..6edd0e840 100644 --- a/src/Dock.Avalonia.Themes.Fluent/DockFluentTheme.axaml.cs +++ b/src/Dock.Avalonia.Themes.Fluent/DockFluentTheme.axaml.cs @@ -1,13 +1,80 @@ // Copyright (c) Wiesław Šoltés. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for details. +using System; +using System.Collections.Generic; +using Avalonia; +using Avalonia.Controls; +using Avalonia.Markup.Xaml; using Avalonia.Styling; +using Dock.Avalonia.Themes; namespace Dock.Avalonia.Themes.Fluent; /// /// The Dock fluent theme. /// -public class DockFluentTheme : Styles +public class DockFluentTheme : Styles, IResourceNode { + private readonly ResourceDictionary _compactStyles; + private DockDensityStyle _densityStyle; + + /// + /// Initializes a new instance of the class. + /// + /// The optional service provider. + public DockFluentTheme(IServiceProvider? serviceProvider = null) + { + AvaloniaXamlLoader.Load(serviceProvider, this); + _compactStyles = (ResourceDictionary)GetAndRemove("CompactStyles"); + + object GetAndRemove(string key) + { + var value = Resources[key] ?? throw new KeyNotFoundException($"Key '{key}' was not found in resources."); + Resources.Remove(key); + return value; + } + } + + /// + /// Identifies the direct property. + /// + public static readonly DirectProperty DensityStyleProperty = + AvaloniaProperty.RegisterDirect( + nameof(DensityStyle), + o => o.DensityStyle, + (o, v) => o.DensityStyle = v); + + /// + /// Gets or sets the density style used by this theme. + /// + public DockDensityStyle DensityStyle + { + get => _densityStyle; + set => SetAndRaise(DensityStyleProperty, ref _densityStyle, value); + } + + /// + /// Handles resource invalidation when theme properties change. + /// + /// The property change payload. + protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) + { + base.OnPropertyChanged(change); + + if (change.Property == DensityStyleProperty) + { + Owner?.NotifyHostedResourcesChanged(new ResourcesChangedEventArgs()); + } + } + + bool IResourceNode.TryGetResource(object key, ThemeVariant? theme, out object? value) + { + if (_densityStyle == DockDensityStyle.Compact && _compactStyles.TryGetResource(key, theme, out value)) + { + return true; + } + + return base.TryGetResource(key, theme, out value); + } } diff --git a/src/Dock.Avalonia.Themes.Fluent/Presets/Ide/Default.axaml b/src/Dock.Avalonia.Themes.Fluent/Presets/Ide/Default.axaml new file mode 100644 index 000000000..6b2a51eef --- /dev/null +++ b/src/Dock.Avalonia.Themes.Fluent/Presets/Ide/Default.axaml @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 0 + diff --git a/src/Dock.Avalonia.Themes.Fluent/Presets/Ide/RiderDark.axaml b/src/Dock.Avalonia.Themes.Fluent/Presets/Ide/RiderDark.axaml new file mode 100644 index 000000000..9a908f220 --- /dev/null +++ b/src/Dock.Avalonia.Themes.Fluent/Presets/Ide/RiderDark.axaml @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 0 + diff --git a/src/Dock.Avalonia.Themes.Fluent/Presets/Ide/RiderLight.axaml b/src/Dock.Avalonia.Themes.Fluent/Presets/Ide/RiderLight.axaml new file mode 100644 index 000000000..618189745 --- /dev/null +++ b/src/Dock.Avalonia.Themes.Fluent/Presets/Ide/RiderLight.axaml @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 0 + diff --git a/src/Dock.Avalonia.Themes.Fluent/Presets/Ide/VsCodeDark.axaml b/src/Dock.Avalonia.Themes.Fluent/Presets/Ide/VsCodeDark.axaml new file mode 100644 index 000000000..cabf983dc --- /dev/null +++ b/src/Dock.Avalonia.Themes.Fluent/Presets/Ide/VsCodeDark.axaml @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 0 + diff --git a/src/Dock.Avalonia.Themes.Fluent/Presets/Ide/VsCodeLight.axaml b/src/Dock.Avalonia.Themes.Fluent/Presets/Ide/VsCodeLight.axaml new file mode 100644 index 000000000..6400f67d7 --- /dev/null +++ b/src/Dock.Avalonia.Themes.Fluent/Presets/Ide/VsCodeLight.axaml @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 0 + diff --git a/src/Dock.Avalonia.Themes.Simple/Accents/Simple.axaml b/src/Dock.Avalonia.Themes.Simple/Accents/Simple.axaml index dcfd2c893..09415f839 100644 --- a/src/Dock.Avalonia.Themes.Simple/Accents/Simple.axaml +++ b/src/Dock.Avalonia.Themes.Simple/Accents/Simple.axaml @@ -21,6 +21,131 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 0 + 26 + 30 + 10 + 12 + 14 + + 24 + 0 + 4,0,4,0 + 4,1,4,0 + 4,2,4,0 + 26 + 24 + 14 + 18 + 16 + 3 + 2,0,0,0 + 12 + 6,4 + 2 + 0,0,2,0 + 2 + 2 + 5 + 8,0 + 0,4,8,4 + 2 + 1 + 5 + 0,0,2,0 + + + + + + + + + + 4 + 2 + 2,0 + 4 + 6 + 40 + 125 + + 6 + 12 + 280 + 520 + 8 + 120 + 320 + 6 + 4 + 4 + 8,0,0,0 + 3 + 4,1 + 10 + 4,2 + 4 + 4 + 4 + 4 + 2 + 10 + 1 + 16 + 8 + 24 + 8 + 14 + 200 + 6 + 10 + 20 + 360 + 640 + 12 + 16 + 28 + 24 + 480 + 12 + 8 + M8.41687 7.57953V2.41851C8.41687 2.18743 8.22932 1.99988 7.99823 1.99988C7.76715 1.99988 7.5796 2.18743 7.5796 2.41851V7.57953H2.41863C2.18755 7.57953 2 7.76708 2 7.99816C2 8.22925 2.18755 8.41679 2.41863 8.41679H7.5796V13.5812C7.5796 13.8123 7.76715 13.9999 7.99823 13.9999C8.22932 13.9999 8.41687 13.8123 8.41687 13.5812V8.41679L13.5799 8.41851C13.811 8.41851 13.9985 8.23096 13.9985 7.99988C13.9985 7.76879 13.811 7.58125 13.5799 7.58125L8.41687 7.57953Z M19,6.41L17.59,5L12,10.59L6.41,5L5,6.41L10.59,12L5,17.59L6.41,19L12,13.41L17.59,19L19,17.59L13.41,12L19,6.41Z diff --git a/src/Dock.Avalonia.Themes.Simple/DensityStyles/Compact.axaml b/src/Dock.Avalonia.Themes.Simple/DensityStyles/Compact.axaml new file mode 100644 index 000000000..4b4a55fb5 --- /dev/null +++ b/src/Dock.Avalonia.Themes.Simple/DensityStyles/Compact.axaml @@ -0,0 +1,61 @@ + + 22 + 22 + 8 + 10 + 12 + + 22 + 0 + 3,0,3,0 + 3,0,3,0 + 3,1,3,0 + + 24 + 22 + 12 + + 16 + 14 + 2 + 1,0,0,0 + + 10 + 4,2 + 1 + 0,0,1,0 + 1 + 1 + 4 + 6,0 + 0,3,6,3 + 1 + 4 + 0,0,1,0 + 3 + 1 + 1,0 + 3 + 3 + 3 + 1 + 9 + 12 + 20 + 6 + 13 + 180 + 5 + 16 + 320 + 560 + 10 + 14 + 24 + 20 + 440 + 10 + 6 + diff --git a/src/Dock.Avalonia.Themes.Simple/DockSimpleTheme.axaml b/src/Dock.Avalonia.Themes.Simple/DockSimpleTheme.axaml index 62024acec..5dd63190d 100644 --- a/src/Dock.Avalonia.Themes.Simple/DockSimpleTheme.axaml +++ b/src/Dock.Avalonia.Themes.Simple/DockSimpleTheme.axaml @@ -47,6 +47,9 @@ + + + diff --git a/src/Dock.Avalonia.Themes.Simple/DockSimpleTheme.axaml.cs b/src/Dock.Avalonia.Themes.Simple/DockSimpleTheme.axaml.cs index 64642844c..1422bc83b 100644 --- a/src/Dock.Avalonia.Themes.Simple/DockSimpleTheme.axaml.cs +++ b/src/Dock.Avalonia.Themes.Simple/DockSimpleTheme.axaml.cs @@ -1,13 +1,80 @@ // Copyright (c) Wiesław Šoltés. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for details. +using System; +using System.Collections.Generic; +using Avalonia; +using Avalonia.Controls; +using Avalonia.Markup.Xaml; using Avalonia.Styling; +using Dock.Avalonia.Themes; namespace Dock.Avalonia.Themes.Simple; /// /// The Dock simple theme. /// -public class DockSimpleTheme : Styles +public class DockSimpleTheme : Styles, IResourceNode { + private readonly ResourceDictionary _compactStyles; + private DockDensityStyle _densityStyle; + + /// + /// Initializes a new instance of the class. + /// + /// The optional service provider. + public DockSimpleTheme(IServiceProvider? serviceProvider = null) + { + AvaloniaXamlLoader.Load(serviceProvider, this); + _compactStyles = (ResourceDictionary)GetAndRemove("CompactStyles"); + + object GetAndRemove(string key) + { + var value = Resources[key] ?? throw new KeyNotFoundException($"Key '{key}' was not found in resources."); + Resources.Remove(key); + return value; + } + } + + /// + /// Identifies the direct property. + /// + public static readonly DirectProperty DensityStyleProperty = + AvaloniaProperty.RegisterDirect( + nameof(DensityStyle), + o => o.DensityStyle, + (o, v) => o.DensityStyle = v); + + /// + /// Gets or sets the density style used by this theme. + /// + public DockDensityStyle DensityStyle + { + get => _densityStyle; + set => SetAndRaise(DensityStyleProperty, ref _densityStyle, value); + } + + /// + /// Handles resource invalidation when theme properties change. + /// + /// The property change payload. + protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) + { + base.OnPropertyChanged(change); + + if (change.Property == DensityStyleProperty) + { + Owner?.NotifyHostedResourcesChanged(new ResourcesChangedEventArgs()); + } + } + + bool IResourceNode.TryGetResource(object key, ThemeVariant? theme, out object? value) + { + if (_densityStyle == DockDensityStyle.Compact && _compactStyles.TryGetResource(key, theme, out value)) + { + return true; + } + + return base.TryGetResource(key, theme, out value); + } } diff --git a/src/Dock.Avalonia.Themes.Simple/Presets/Ide/Default.axaml b/src/Dock.Avalonia.Themes.Simple/Presets/Ide/Default.axaml new file mode 100644 index 000000000..4509fa857 --- /dev/null +++ b/src/Dock.Avalonia.Themes.Simple/Presets/Ide/Default.axaml @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 0 + diff --git a/src/Dock.Avalonia.Themes.Simple/Presets/Ide/RiderDark.axaml b/src/Dock.Avalonia.Themes.Simple/Presets/Ide/RiderDark.axaml new file mode 100644 index 000000000..9a908f220 --- /dev/null +++ b/src/Dock.Avalonia.Themes.Simple/Presets/Ide/RiderDark.axaml @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 0 + diff --git a/src/Dock.Avalonia.Themes.Simple/Presets/Ide/RiderLight.axaml b/src/Dock.Avalonia.Themes.Simple/Presets/Ide/RiderLight.axaml new file mode 100644 index 000000000..618189745 --- /dev/null +++ b/src/Dock.Avalonia.Themes.Simple/Presets/Ide/RiderLight.axaml @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 0 + diff --git a/src/Dock.Avalonia.Themes.Simple/Presets/Ide/VsCodeDark.axaml b/src/Dock.Avalonia.Themes.Simple/Presets/Ide/VsCodeDark.axaml new file mode 100644 index 000000000..cabf983dc --- /dev/null +++ b/src/Dock.Avalonia.Themes.Simple/Presets/Ide/VsCodeDark.axaml @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 0 + diff --git a/src/Dock.Avalonia.Themes.Simple/Presets/Ide/VsCodeLight.axaml b/src/Dock.Avalonia.Themes.Simple/Presets/Ide/VsCodeLight.axaml new file mode 100644 index 000000000..6400f67d7 --- /dev/null +++ b/src/Dock.Avalonia.Themes.Simple/Presets/Ide/VsCodeLight.axaml @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 0 + diff --git a/src/Dock.Avalonia/Themes/DockDensityStyle.cs b/src/Dock.Avalonia/Themes/DockDensityStyle.cs new file mode 100644 index 000000000..f5d788cc5 --- /dev/null +++ b/src/Dock.Avalonia/Themes/DockDensityStyle.cs @@ -0,0 +1,20 @@ +// Copyright (c) Wiesław Šoltés. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for details. + +namespace Dock.Avalonia.Themes; + +/// +/// Defines density presets for Dock control themes. +/// +public enum DockDensityStyle +{ + /// + /// Uses default control sizing. + /// + Normal, + + /// + /// Uses compact sizing for tighter layouts. + /// + Compact +} diff --git a/tests/Dock.Avalonia.Themes.UnitTests/ThemeDensityControlSizingTests.cs b/tests/Dock.Avalonia.Themes.UnitTests/ThemeDensityControlSizingTests.cs new file mode 100644 index 000000000..6d32e6fd5 --- /dev/null +++ b/tests/Dock.Avalonia.Themes.UnitTests/ThemeDensityControlSizingTests.cs @@ -0,0 +1,221 @@ +using System.Linq; +using Avalonia; +using Avalonia.Controls; +using Avalonia.Controls.Shapes; +using Avalonia.Headless.XUnit; +using Avalonia.Styling; +using Avalonia.Threading; +using Avalonia.VisualTree; +using Dock.Avalonia.Controls; +using Dock.Avalonia.Controls.Overlays; +using Dock.Avalonia.Themes; +using Dock.Avalonia.Themes.Fluent; +using Dock.Avalonia.Themes.Simple; +using Xunit; + +namespace Dock.Avalonia.Themes.UnitTests; + +[Collection(ThemeResourceIsolationCollection.Name)] +public class ThemeDensityControlSizingTests +{ + [AvaloniaFact] + public void CompactDensity_Should_Update_TabItem_MinHeights_For_All_Themes() + { + foreach (var useFluentTheme in new[] { true, false }) + { + var documentTab = new DocumentTabStripItem(); + var documentWindow = ShowWithTheme( + documentTab, + useFluentTheme + ? new DockFluentTheme { DensityStyle = DockDensityStyle.Compact } + : new DockSimpleTheme { DensityStyle = DockDensityStyle.Compact }); + try + { + Assert.Equal(22d, documentTab.MinHeight); + } + finally + { + documentWindow.Close(); + } + + var toolTab = new ToolTabStripItem(); + var toolWindow = ShowWithTheme( + toolTab, + useFluentTheme + ? new DockFluentTheme { DensityStyle = DockDensityStyle.Compact } + : new DockSimpleTheme { DensityStyle = DockDensityStyle.Compact }); + try + { + Assert.Equal(0d, toolTab.MinHeight); + Assert.Equal(new Thickness(3, 0, 3, 0), toolTab.Padding); + } + finally + { + toolWindow.Close(); + } + } + } + + [AvaloniaFact] + public void NormalDensity_Should_Keep_Default_Tool_Tab_Metrics_For_All_Themes() + { + foreach (var useFluentTheme in new[] { true, false }) + { + var toolTab = new ToolTabStripItem(); + var toolWindow = ShowWithTheme( + toolTab, + useFluentTheme + ? new DockFluentTheme { DensityStyle = DockDensityStyle.Normal } + : new DockSimpleTheme { DensityStyle = DockDensityStyle.Normal }); + try + { + Assert.Equal(0d, toolTab.MinHeight); + Assert.Equal(new Thickness(4, 1, 4, 0), toolTab.Padding); + } + finally + { + toolWindow.Close(); + } + } + } + + [AvaloniaFact] + public void Density_Should_Update_Close_And_Chrome_Button_Themes_For_All_Themes() + { + AssertThemeButtonSizing(new DockFluentTheme { DensityStyle = DockDensityStyle.Normal }, 14d, 18d, 16d); + AssertThemeButtonSizing(new DockSimpleTheme { DensityStyle = DockDensityStyle.Normal }, 14d, 18d, 16d); + + AssertThemeButtonSizing(new DockFluentTheme { DensityStyle = DockDensityStyle.Compact }, 12d, 16d, 14d); + AssertThemeButtonSizing(new DockSimpleTheme { DensityStyle = DockDensityStyle.Compact }, 12d, 16d, 14d); + } + + [AvaloniaFact] + public void Theme_Tokens_Should_Bind_To_Key_Control_Template_Parts() + { + AssertTemplatePartSizing(new DockFluentTheme { DensityStyle = DockDensityStyle.Normal }, expectedGripHeight: 5d, expectedStatusIconSize: 10d, expectedTargetSelectorSize: 40d, expectedDialogTitleFontSize: 16d, expectedDialogCloseButtonSize: 28d); + AssertTemplatePartSizing(new DockSimpleTheme { DensityStyle = DockDensityStyle.Normal }, expectedGripHeight: 5d, expectedStatusIconSize: 10d, expectedTargetSelectorSize: 40d, expectedDialogTitleFontSize: 16d, expectedDialogCloseButtonSize: 28d); + + AssertTemplatePartSizing(new DockFluentTheme { DensityStyle = DockDensityStyle.Compact }, expectedGripHeight: 4d, expectedStatusIconSize: 9d, expectedTargetSelectorSize: 40d, expectedDialogTitleFontSize: 14d, expectedDialogCloseButtonSize: 24d); + AssertTemplatePartSizing(new DockSimpleTheme { DensityStyle = DockDensityStyle.Compact }, expectedGripHeight: 4d, expectedStatusIconSize: 9d, expectedTargetSelectorSize: 40d, expectedDialogTitleFontSize: 14d, expectedDialogCloseButtonSize: 24d); + } + + private static void AssertThemeButtonSizing(Styles theme, double expectedCloseSize, double expectedChromeWidth, double expectedChromeHeight) + { + var host = new StackPanel(); + var closeButton = new Button(); + var chromeButton = new Button(); + + host.Children.Add(closeButton); + host.Children.Add(chromeButton); + + var window = ShowWithTheme(host, theme); + try + { + Assert.True(host.TryFindResource("DocumentCloseButtonTheme", out var closeThemeResource)); + closeButton.Theme = Assert.IsType(closeThemeResource); + + Assert.True(host.TryFindResource("ChromeButton", out var chromeThemeResource)); + chromeButton.Theme = Assert.IsType(chromeThemeResource); + + closeButton.ApplyTemplate(); + chromeButton.ApplyTemplate(); + Dispatcher.UIThread.RunJobs(); + + Assert.Equal(expectedCloseSize, closeButton.Width); + Assert.Equal(expectedCloseSize, closeButton.Height); + Assert.Equal(expectedChromeWidth, chromeButton.Width); + Assert.Equal(expectedChromeHeight, chromeButton.Height); + } + finally + { + window.Close(); + } + } + + private static void AssertTemplatePartSizing( + Styles theme, + double expectedGripHeight, + double expectedStatusIconSize, + double expectedTargetSelectorSize, + double expectedDialogTitleFontSize, + double expectedDialogCloseButtonSize) + { + var host = new StackPanel(); + var toolChrome = new ToolChromeControl(); + var dragPreview = new DragPreviewControl + { + Status = "Dock" + }; + var dockTarget = new DockTarget(); + var dialogShell = new DialogShellControl + { + Title = "Dialog", + IsCloseVisible = true + }; + + host.Children.Add(toolChrome); + host.Children.Add(dragPreview); + host.Children.Add(dockTarget); + host.Children.Add(dialogShell); + + var window = ShowWithTheme(host, theme); + try + { + toolChrome.ApplyTemplate(); + dragPreview.ApplyTemplate(); + dockTarget.ApplyTemplate(); + dialogShell.ApplyTemplate(); + toolChrome.UpdateLayout(); + dragPreview.UpdateLayout(); + dockTarget.UpdateLayout(); + dialogShell.UpdateLayout(); + Dispatcher.UIThread.RunJobs(); + + var gripGrid = FindNamedControl(toolChrome, "PART_Grid"); + var statusIcon = FindNamedControl(dragPreview, "PART_StatusIcon"); + var topSelector = FindNamedControl(dockTarget, "PART_TopSelector"); + var dialogCloseButton = dialogShell.GetVisualDescendants().OfType