diff --git a/THIRD-PARTY-NOTICES.txt b/THIRD-PARTY-NOTICES.txt index 2ff3df80a..200e8cdb2 100644 --- a/THIRD-PARTY-NOTICES.txt +++ b/THIRD-PARTY-NOTICES.txt @@ -17,6 +17,7 @@ Summary of licenses present: - BSD 3-Clause License - BSD 2-Clause License - Microsoft Public License (Ms-PL) + - SIL Open Font License 1.1 (bundled fonts) - GPL v2.0 WITH Linking Exception (libgit2 native binaries only — see note) ================================================================================ @@ -57,9 +58,40 @@ Permission to copy, use, modify, sell and distribute this software is granted. This software is provided "as is" without express or implied warranty, and with no claim as to its suitability for any purpose. +================================================================================ +FILE ICONS +================================================================================ +The file icons in src/OneWare.Core/Styles/FileIcons.axaml were +converted from the Material Icon Theme for Visual Studio Code (version 5.38.1): +https://github.com/material-extensions/vscode-material-icon-theme +Copyright (c) 2025 Material Extensions. They are licensed under the MIT License +reproduced below. + +================================================================================ +FONTS +================================================================================ +The fonts in src/OneWare.Core/Assets/Fonts and the Inter font (bundled via the +Avalonia.Fonts.Inter package) are licensed under the SIL Open Font License, +Version 1.1, reproduced below: + - JetBrains Mono NL + Copyright 2020 The JetBrains Mono Project Authors + (https://github.com/JetBrains/JetBrainsMono) + - Fira Code + Copyright (c) 2014, The Fira Code Project Authors + (https://github.com/tonsky/FiraCode) + - Cascadia Mono + Copyright (c) 2019 - Present, Microsoft Corporation, + with Reserved Font Name Cascadia Code. + (https://github.com/microsoft/cascadia-code) + - Inter + Copyright (c) 2016 The Inter Project Authors (https://github.com/rsms/inter) + ================================================================================ COMPONENTS UNDER THE MIT LICENSE ================================================================================ + - Material Icon Theme file icons (version 5.38.1) + Copyright (c) 2025 Material Extensions + Source: https://github.com/material-extensions/vscode-material-icon-theme - VSCode-SystemVerilog syntax grammar (revision e6a5a2b76055dd7acbdc7420060e0314cb86e8dd) Copyright (c) 2018 Eirik Prestegårdshus Source: https://github.com/mshr-h/vscode-verilog-hdl-support @@ -465,6 +497,96 @@ change. To the extent permitted under your local laws, the contributors exclude the implied warranties of merchantability, fitness for a particular purpose and non-infringement. +-------------------------------------------------------------------------------- +SIL Open Font License 1.1 +-------------------------------------------------------------------------------- +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. + ================================================================================ END OF THIRD-PARTY NOTICES ================================================================================ diff --git a/build/props/Base.props b/build/props/Base.props index 12840f432..4f5581eee 100644 --- a/build/props/Base.props +++ b/build/props/Base.props @@ -1,7 +1,7 @@  - 1.0.40 + 1.0.41 $(StudioVersion) $(StudioVersion) $(StudioVersion) diff --git a/build/props/Markdown.Avalonia.props b/build/props/Markdown.Avalonia.props index e9196c816..0edf5f50e 100644 --- a/build/props/Markdown.Avalonia.props +++ b/build/props/Markdown.Avalonia.props @@ -1,8 +1,8 @@  - - - + + + diff --git a/demo/OneWare.Demo.Desktop/Program.cs b/demo/OneWare.Demo.Desktop/Program.cs index 809f809dd..dcbe153b9 100644 --- a/demo/OneWare.Demo.Desktop/Program.cs +++ b/demo/OneWare.Demo.Desktop/Program.cs @@ -6,8 +6,8 @@ using System.Runtime.InteropServices; using Avalonia; using Avalonia.Dialogs; -using Avalonia.Media; using Microsoft.Extensions.Logging; +using OneWare.Core.Extensions; using OneWare.Essentials.Helpers; using OneWare.Essentials.Services; @@ -29,11 +29,7 @@ private static AppBuilder BuildAvaloniaApp() ? Environment.OSVersion.Version.Build >= 22000 ? 8 : 0 : 0 }) - //.WithInterFont() - .With(new FontManagerOptions - { - DefaultFamilyName = "avares://OneWare.Core/Assets/Fonts#Noto Sans" - }) + .WithOneWareFonts() .LogToTrace(); if (DemoApp.SettingsService.GetSettingValue("Experimental_UseManagedFileDialog")) diff --git a/docs/PluginDevelopment.md b/docs/PluginDevelopment.md index e70c9fcc2..b7d0df35d 100644 --- a/docs/PluginDevelopment.md +++ b/docs/PluginDevelopment.md @@ -106,6 +106,13 @@ You can generate `compatibility.txt` automatically during build by marking depen Use `serviceProvider.Resolve()` or `ContainerLocator.Current` to resolve OneWare services. +## Styling + +Plugins share the host's design system (tokens and style classes like `primary`, `card`, `badge`, `h3`, `muted`). +Use the classes instead of inline colors, borders or font sizes. For monochrome icons use +``, which follows the surrounding foreground color. +See [Styling.md](./Styling.md). + ## OneWare.Essentials interfaces Below is a concise guide to the public interfaces in `OneWare.Essentials`. Use them as stable @@ -398,7 +405,7 @@ Common extension patterns used across the built-in modules: `MainWindow_MainMenu/File/New`. - Show tools or documents using `IMainDockService.Show()` and `IMainDockService.OpenFileAsync`. - Register settings for your module via `ISettingsService.RegisterSetting`. -- Attach file icons with `IFileIconService.RegisterFileIcon`. +- Attach file icons with `IFileIconService.RegisterFileIcon`. Prefer the built-in `FileIcon.*` resources (see `docs/Styling.md`). - Emit build or tool output via `IOutputService.WriteLine` and report errors via `IErrorService`. @@ -468,7 +475,9 @@ Project-specific files: - `RegisterPreCompileStep()` for pre-compile hooks. - `RegisterNodeProvider()` for HDL node extraction. - `RegisterProjectPropertyMigration(...)` for project property migrations. -- `RegisterProjectEntryModification(Action)` for custom project explorer adornments. +- `RegisterProjectEntryModification(Action)` for custom project explorer adornments. To mark a file + (e.g. top entity, testbench), prefer `entry.AddTag(key, new ProjectExplorerTag("Top", ProjectExplorerTagKind.Accent))` + / `entry.RemoveTag(key)`, which shows a pill after the name, over icon overlays. ### Hardware packages @@ -560,7 +569,9 @@ Use `IWindowService.RegisterUiExtension` to add UI to these extension points: - `UniversalFpgaToolBar_PinPlannerMenuExtension`: pin planner menu. - `UniversalFpgaToolBar_DownloaderConfigurationExtension`: download configuration area. - `CompileWindow_TopRightExtension`: pin planner window (top right region). -- `EditView_Top`: top editor panel (used for test bench toolbar). +- `EditView_Top`: top editor panel (used for test bench toolbar). Use the `Border.editor-bar` / + `WrapPanel.editor-bar-items` style classes so the bar matches the others and wraps on small screens + (see `docs/Styling.md`, "Editor bar"). ## Suggested validation and troubleshooting diff --git a/docs/Styling.md b/docs/Styling.md new file mode 100644 index 000000000..6cd138c25 --- /dev/null +++ b/docs/Styling.md @@ -0,0 +1,279 @@ +# Styling Guide + +OneWare uses Avalonia's **SimpleTheme** with a shared design layer on top of it. The design layer lives in +`OneWare.Essentials`, so the host app and every plugin get the same look. + +``` +src/OneWare.Essentials/Styles/ +├── OneWareTheme.axaml entry point (tokens + all control styles) +├── Tokens.axaml shared tokens (accent, status, radius, spacing, font sizes) +├── TokensDark.axaml dark surface/foreground colors +├── TokensLight.axaml light surface/foreground colors +├── Icons.axaml monochrome icon geometries (Icon.*) +├── StyleGallery.axaml showcase of every class (Help → Style Gallery in Debug builds) +└── Controls/ one file per control family +``` + +The host app includes `OneWareTheme.axaml` once, in `OneWare.Core/App.axaml`. Plugins **do not** need to include it, +because they get it from the host at runtime. + +## Rules + +1. **Use classes, not inline visuals.** Do not set `Background`, `Foreground`, `BorderBrush`, `BorderThickness`, + `CornerRadius`, `FontSize`, `FontWeight` or `Padding` inline on views when a class covers it. + `Margin`, alignment, sizes and grid layout stay inline. +2. **No hex colors in views.** If you need a color, use a token through `{DynamicResource ...}`. Only colors that come + from data are exempt, for example label colors, waveform colors or overlay colors. +3. **Always use `DynamicResource`** for tokens so Light/Dark switching and accent changes work. +4. **No local `` for generic looks.** Local styles are fine for behavior that only one view has, + such as template tweaks or data-driven states. If two views need the same look, add a class to + `OneWare.Essentials/Styles/Controls` instead. +5. **Inside a `DataTemplate`/`ControlTemplate`, a `{DynamicResource}` attribute does not override a class style.** + Avalonia applies it with `Template` priority, which is lower than a class selector (`StyleTrigger`), so + `` in an item template keeps the card background. + Override the property with a style instead, for example a `Style Selector="Border.card.my-item"` in the + parent's `Styles`, or put the brush on a child `Panel` that has no class. + +## Tokens + +Color keys keep the original [Avalonia SimpleTheme names](https://github.com/AvaloniaUI/Avalonia/blob/main/src/Avalonia.Themes.Simple/Accents/Base.xaml). +Each key exists as a `...Color` and a `...Brush`. + +| Key | Use | +|---|---| +| `ThemeBackgroundBrush` | Panel / window background | +| `ThemeControlLowBrush` | Deepest surface: editors, lists, insets | +| `ThemeControlMidBrush` | Input surfaces (TextBox, ComboBox) | +| `ThemeControlMidHighBrush` | Progress track | +| `ThemeButtonSecondaryBrush` / `ThemeButtonSecondaryHoverBrush` | Secondary button fill (opaque and slightly lighter than the background and elevated cards in dark, so it reads raised like a primary button and stays visible over images) | +| `ThemeControlHighBrush` / `ThemeControlVeryHighBrush` | Strong neutral fills / glyphs | +| `ThemeElevatedBrush` | Cards, popups, menus, tooltips | +| `ThemeControlHighlightMidBrush` / `...HighBrush` / `...LowBrush` | Hover / pressed overlays | +| `ThemeBorderLowBrush` / `...MidBrush` / `...HighBrush` | Subtle / default / strong borders | +| `ThemeForegroundBrush` / `ThemeForegroundLowBrush` | Primary / secondary text | +| `ThemeAccentBrush`, `ThemeAccentBrush2-4`, `ThemeAccentLowBrush` | Accent (100 %, 60 %, 40 %, 20 %, 14 %) | +| `HighlightBrush`, `HighlightForegroundBrush` | Accent fill and the text drawn on it | +| `HighlightForegroundLowBrush`, `HighlightForegroundMidBrush` | Hover / pressed overlay for controls on an accent fill | +| `SuccessBrush`, `WarningBrush`, `ErrorBrush`, `InfoBrush`, `NeutralBrush` | Status colors (`NeutralBrush` is the grey default badge fill) | +| `SuccessLowBrush`, `WarningLowBrush`, `ErrorLowBrush`, `InfoLowBrush` | Tinted status backgrounds | + +Non-color tokens: + +| Key | Value | +|---|---| +| `ThemeCornerRadiusSmall` / `ThemeCornerRadius` / `ThemeCornerRadiusLarge` / `ThemeCornerRadiusPill` | 3 / 4 / 6 / 999 | +| `ThemeControlHeightSmall` / `ThemeControlHeight` / `ThemeControlHeightLarge` | 20 / 24 / 30 | +| `ThemeIconSizeSmall` / `ThemeIconSize` / `ThemeIconSizeLarge` | 12 / 16 / 20 | +| `ThemeSpacingXSmall` … `ThemeSpacingXLarge` (double) | 2 / 4 / 8 / 12 / 16 | +| `ThemePaddingSmall` / `ThemePadding` / `ThemePaddingLarge` / `ThemePaddingXLarge` (Thickness) | 4 / 8 / 12 / 16 | +| `FontSizeBadge` / `Small` / `Normal` / `Medium` / `Large` / `XLarge` / `XXLarge` | 10 / 11 / 12 / 13 / 16 / 20 / 26 | + +Legacy keys such as `GreenAccent` are kept as aliases. + +Fonts: the UI uses the platform font (`$Default`, also available as `ContentControlThemeFontFamily`): Segoe UI on +Windows, SF Pro on macOS, and the bundled Inter on Linux and in the browser (set up by `AppBuilder.WithOneWareFonts()`). +Don't set a UI `FontFamily` inline. Monospace text uses `{DynamicResource EditorFont}` (or the `mono` class), which follows +the editor font setting: bundled JetBrains Mono NL (default), Fira Code, Cascadia Mono, or any installed monospace font. + +Inputs (`TextBox`, `ComboBox`, `NumericUpDown`) get a default `Height` of `ThemeControlHeight`. It is a normal +`Height`, not a `MinHeight`, so an inline `Height` on a view still wins. Multiline text boxes reset it to `NaN`. + +## Icons + +Monochrome icons are `StreamGeometry` resources named `Icon.*` in `OneWare.Essentials/Styles/Icons.axaml`. +Render them with `PathIcon`. They take the `Foreground` of their container, so they turn white on a `primary` +button, follow hover and selection colors, and switch with the theme automatically. + +```xml + + + +``` + +| `PathIcon` class | Effect | +|---|---| +| `small` / `large` | 12 / 20 px (default 16 px) | +| `muted` | Secondary foreground | +| `accent` `success` `warning` `error` | Colored icon | + +- All geometries are normalized to a 24×24 frame with consistent padding, so any size works. +- Help → Style Gallery (Debug builds) lists every icon with its name. +- Plugins that compile against an older `OneWare.Essentials` package should use `{DynamicResource Icon.X}`. + `StaticResource` fails when the key is not in the referenced package. +- Multi-color icons, such as file types, debugger actions and status badges, stay `DrawingImage` resources in + `OneWare.Core/Styles/Icons.axaml` and are used with ``. +- File type icons are `FileIcon.*` `DrawingImage` resources in `OneWare.Core/Styles/FileIcons.axaml`, + converted from the MIT-licensed [Material Icon Theme](https://github.com/material-extensions/vscode-material-icon-theme) + for VS Code. Register them for extensions with `IFileIconService.RegisterFileIcon("FileIcon.Json", ".json")`. + To add one, convert its SVG into a `DrawingGroup` with a transparent frame the size of the SVG `viewBox`, and + use `F1` geometries, because SVG fills are nonzero. +- The old single-color `DrawingImage` keys (for example `BoxIcons.RegularSearch` and `Material.SettingsOutline`) + still exist as thin wrappers around the `Icon.*` geometries. They keep `IconModel` and C# code working, but new + views should use `PathIcon`. +- To add an icon, add an `Icon.Name` geometry to `Icons.axaml`. The geometry must include the + `M0 0L0 0ZM24 24L24 24Z` frame prefix. + +## Classes + +### Text (`TextBlock`, `SelectableTextBlock`) + +| Class | Look | +|---|---| +| `h1` `h2` `h3` `h4` | Headings (26 / 20 / 16 / 13, SemiBold) | +| `overline` | Small SemiBold secondary section label | +| `muted` | Secondary foreground | +| `caption` | Small + secondary foreground | +| `mono` | Editor font | +| `semibold` `bold` | Font weight | +| `accent` `success` `warning` `error` | Colored text | + +### Buttons (`Button`, `ToggleButton`) + +| Class | Look | +|---|---| +| *(none)* / `ghost` | Transparent toolbar button with hover highlight | +| `secondary` | Bordered neutral button (legacy: `RoundButton`, `WindowButton`, `SecondaryButton`) | +| `primary` | Solid accent button (legacy: `PrimaryButton`) | +| `danger` | Solid error button | +| `link` | Text-only accent button | +| `icon` | Square icon button, combine with the above | +| `small` / `large` | Size modifiers | + +Checked `ToggleButton`s get an accent tint automatically. Use `` for dialog button rows. +`secondary`, `primary`, `danger` and `ghost` buttons get the input height as a `MinHeight` (`ThemeControlHeight`, or +`ThemeControlHeightSmall` / `ThemeControlHeightLarge` with `small` / `large`) and are centered vertically, so they line up +with text boxes and combo boxes in a row. Larger content (images, multi-line text) grows the button, no `Height="NaN"` +needed. To go below the minimum, set `MinHeight="0"` (or a smaller `MinHeight`) together with `Height`. `icon` buttons are +a fixed square instead, so an inline `Width` / `Height` of any size wins. + +### Surfaces (`Border`) + +| Class | Look | +|---|---| +| `card` (+ `compact`) | Elevated, bordered, rounded block | +| `panel` | Flat bordered block on the panel background | +| `inset` | Recessed area (lists, previews) | +| `inset-content` | Put directly inside `inset` when the content has its own backgrounds; clips it inside the rounded outline | +| `header` / `footer` | Bar with a bottom / top divider | +| `editor-bar` | Contextual bar above an editor (`EditView_Top` extensions), see below | +| `statusbar` | Accent strip at the bottom of the window; text, icons, buttons and top-level menu items are white in both themes (popups opened from it keep the normal colors) | +| `divider` (+ `vertical`) | 1 px line | +| `overlay` | Floating popup surface with shadow | +| `badge` (+ `accent` `success` `warning` `error`, `small` for dense rows) | Solid status pill (neutral grey by default) with small white text, dark on `success`; put a `TextBlock` inside. Not a container for buttons, use `inset` for that | +| `ItemsControl.tags` | Row of `badge small` pills for `ProjectExplorerTag` items (project explorer file tags) | +| `callout` (+ `success` `warning` `error`) | Inline info box | +| `empty-state` | Centered placeholder | +| `interactive` / `selected` | Hover and selection modifiers for clickable cards | +| `RoundBorder`, `ValidBorder`, `InvalidBorder` | Legacy | + +Docking (`OneWare.Core/Styles/Dock.axaml`): every tool and document dock sits in a rounded, clipped card +(`Border.dock-card`, outline drawn on top by `Border.dock-card-outline`), with the 4 px splitters as gaps between +cards. Tool headers have a 25 px surface plus a neutral 1 px separator towards the content (`DockToolChromeHeaderHeight`, +26 px in total) in both states; while a tool is active its header surface and the surrounding outline +(`Border.dock-card-header-outline`) use the accent color. +Document tabs sit above the card on the background; the accent separator forms the card's top edge. Tool and document views therefore need no outer +border or corner radius of their own, and should not add margins to keep content away from the card edge. + +### Inputs and lists + +- `TextBox`: `small`, `bare` (no border/background), `multiline` (legacy `MultilineTextBox`), + `search` (compact padding for `InnerLeftContent` / `InnerRightContent` icons) +- `SearchBox` (`OneWare.Essentials.Controls`): the standard search/filter field, a single `TextBox` with a leading + search icon and a clear button. Set `SearchButtonVisible="False"` for live filtering, and bind `IsBusy` to show a + spinner. Do not wrap it in extra borders or give it a background. Put it in a strip with `Padding="4"`. +- `ComboBox`: `small`, `ghost` (borderless and transparent like a toolbar button, highlighted on hover / while open) +- `DropDownButton`: styled like a `ComboBox` field (rounded corners, compact chevron, accent border while the flyout + is open). Use it for "Sort by: …" style pickers that open a `MenuFlyout`. +- `NumericUpDown`: `small`. Same frame and padding as `TextBox`, with a compact up/down column on the right. +- `Slider`: rounded track with an accent-filled range and a ringed thumb. `TickPlacement` / `TickFrequency` / `Ticks` + draw tick marks. Set `Foreground` (fill) or `Background` (track) to recolor it. +- `ListBox`: `transparent`, `InvisibleSelection`. By default it is an inset field (control background, low border, + same corner radius as `TextBox`). Setting `BorderThickness="0"` makes it square for lists that sit edge-to-edge in a panel. + Items are rounded rows with a hover highlight and an accent selection. +- `Expander`: `card`. By default it is a flat header row with a rotating chevron and a ghost-button hover, with no padding + around the content. `card` turns it into a bordered section with a divider between header and content. Set + `HorizontalContentAlignment="Stretch"` to stretch custom header content (e.g. a trailing badge). +- `ProgressBar`: a flat, square-cornered track with an accent fill, 6 px thick by default. Use `large` (12 px) for prominent + progress such as downloads and installs; set `Height` only when a custom thickness is really needed. Don't set + `Background` or `CornerRadius`. `ShowProgressText` shows the percentage next to the bar. `fill` stretches the bar over its container, e.g. as a + progress background behind a button's content. It has a transparent track and a subtle overlay fill, which turns + light on `primary` / `danger` buttons. +- `TreeDataGrid`: themed app-wide (`Controls/TreeDataGrid.axaml`), so no local theme include is needed. It has compact + rows with a hover highlight, an accent selection and a 16 px indent per level. The Project Explorer and Problems panels use it. + +### Flyouts + +Every `Flyout` is rounded and elevated, with 8 px padding. Do not set `FlyoutPresenterTheme`, and do not add an outer +margin or background to the content. Choose a variant with `FlyoutPresenterClasses`: + +| Class | Use | +|---|---| +| (none) | Text, help/markdown, and small forms | +| `list` | `ListBox` / `MenuItem` pickers: 4 px padding like menus, and the `ListBox` becomes transparent | +| `flush` | Content that brings its own layout (headers, color picker); no padding, clipped to the rounded corners | + +```xml + + + +``` + +`FlyoutNoPadding` is a legacy alias for the default flyout. + +### Layout (`StackPanel`) + +| Class | Look | +|---|---| +| `toolbar` (legacy `ToolBar`) | Horizontal, 24 px high, 4 px spacing | +| `editor-bar-items` (`WrapPanel`) | Groups of an editor bar; wraps onto new lines when the editor is narrow | +| `form` | Vertical, 8 px spacing | +| `form-row` | Horizontal, 8 px spacing, centered labels | +| `section` | 4 px spacing | +| `page` | 12 px spacing and 16 px outer margin | +| `spacing-small` / `spacing` / `spacing-large` | 4 / 8 / 12 px spacing | + +### Editor bar + +Toolbars above an editor (`EditView_Top` extensions, test bench simulator options) share one layout: the primary +action first, then labelled option groups. Groups wrap onto new lines on narrow editors instead of being clipped. +Labels inside `form-row` groups are muted, rarely used options go into a settings flyout (`Button.icon`). + +```xml + + + + + + + + + +``` + +Nested extensions (`TestBenchToolbarTopUiExtension`) provide only a `WrapPanel.editor-bar-items` without the outer +border; the host bar already draws it. + +## Example + +```xml + + + + + + + + + diff --git a/src/OneWare.ApplicationCommands/Views/CommandManagerView.axaml b/src/OneWare.ApplicationCommands/Views/CommandManagerView.axaml index c772d0b1a..a9a92d7b2 100644 --- a/src/OneWare.ApplicationCommands/Views/CommandManagerView.axaml +++ b/src/OneWare.ApplicationCommands/Views/CommandManagerView.axaml @@ -23,7 +23,7 @@ @@ -54,10 +54,14 @@ Foreground="{DynamicResource ThemeForegroundLowBrush}" VerticalAlignment="Center" Margin="0 0 5 0" /> + + + diff --git a/src/OneWare.Chat/Views/ChatMessages/ChatMessagePermissionRequestView.axaml b/src/OneWare.Chat/Views/ChatMessages/ChatMessagePermissionRequestView.axaml index 83b0935ff..0129863ca 100644 --- a/src/OneWare.Chat/Views/ChatMessages/ChatMessagePermissionRequestView.axaml +++ b/src/OneWare.Chat/Views/ChatMessages/ChatMessagePermissionRequestView.axaml @@ -13,21 +13,21 @@ - - diff --git a/src/OneWare.Core/Views/Controls/VariableControlView.axaml b/src/OneWare.Core/Views/Controls/VariableControlView.axaml index e69beb6c6..44db9eb33 100644 --- a/src/OneWare.Core/Views/Controls/VariableControlView.axaml +++ b/src/OneWare.Core/Views/Controls/VariableControlView.axaml @@ -4,7 +4,7 @@ xmlns:models="clr-namespace:OneWare.Core.Models" x:DataType="models:ObjectValueModel"> + Background="{DynamicResource ThemeControlLowBrush}"> @@ -59,7 +59,7 @@ - - + diff --git a/src/OneWare.Core/Views/DockViews/WelcomeScreenView.axaml b/src/OneWare.Core/Views/DockViews/WelcomeScreenView.axaml index 471aee42b..6cf6a4818 100644 --- a/src/OneWare.Core/Views/DockViews/WelcomeScreenView.axaml +++ b/src/OneWare.Core/Views/DockViews/WelcomeScreenView.axaml @@ -49,10 +49,10 @@ - + - - + + + - + - + - - - - - + + - - - - - + - + - - - - + \ No newline at end of file diff --git a/src/OneWare.Core/Views/Windows/MainSingleView.axaml b/src/OneWare.Core/Views/Windows/MainSingleView.axaml index a125da48e..ca0db4690 100644 --- a/src/OneWare.Core/Views/Windows/MainSingleView.axaml +++ b/src/OneWare.Core/Views/Windows/MainSingleView.axaml @@ -1,4 +1,4 @@ - - + - + - @@ -61,8 +60,7 @@ + Text="{Binding MainDockService.CurrentDocument.Extension}" /> @@ -73,8 +71,7 @@ - @@ -83,7 +80,7 @@ CommandParameter="{x:Type windows:ArduinoBoardManagerWindow}"> Arduino Board Manager - + @@ -94,17 +91,14 @@ - + - + - - + @@ -120,10 +114,9 @@ - - + + @@ -133,7 +126,7 @@ + Classes="statusbar"> + Text="{Binding ApplicationStateService.ActiveProcess.StatusMessage}" /> - @@ -75,9 +76,7 @@ - + @@ -85,17 +84,16 @@ - + - + - - + diff --git a/src/OneWare.Core/Views/Windows/MessageBoxWindow.axaml b/src/OneWare.Core/Views/Windows/MessageBoxWindow.axaml index a6e064b80..04bbd6d6b 100644 --- a/src/OneWare.Core/Views/Windows/MessageBoxWindow.axaml +++ b/src/OneWare.Core/Views/Windows/MessageBoxWindow.axaml @@ -11,19 +11,12 @@ SizeToContent="Height" MinHeight="100" PrefWidth="350" - Padding="4" + Padding="{DynamicResource ThemePaddingSmall}" Focusable="True" Foreground="{DynamicResource ThemeForegroundBrush}" Name="MessageWindow" x:CompileBindings="True" x:DataType="windows:MessageBoxViewModel" WindowStartupLocation="CenterOwner"> - - - - @@ -67,7 +60,7 @@ Classes="WindowButton" Classes.PrimaryButton="{Binding Model.Style, Converter={x:Static converters:SharedConverters.ComparisonConverter}, ConverterParameter={x:Static models:MessageBoxButtonStyle.Primary}}" Classes.SecondaryButton="{Binding Model.Style, Converter={x:Static converters:SharedConverters.ComparisonConverter}, ConverterParameter={x:Static models:MessageBoxButtonStyle.Secondary}}" - Classes.MessageBoxDanger="{Binding Model.Style, Converter={x:Static converters:SharedConverters.ComparisonConverter}, ConverterParameter={x:Static models:MessageBoxButtonStyle.Danger}}" + Classes.danger="{Binding Model.Style, Converter={x:Static converters:SharedConverters.ComparisonConverter}, ConverterParameter={x:Static models:MessageBoxButtonStyle.Danger}}" IsDefault="{Binding IsDefault}" IsCancel="{Binding IsCancel}" Command="{Binding #MessageWindow.((windows:MessageBoxViewModel)DataContext).ButtonCommand, FallbackValue={x:Null}}" diff --git a/src/OneWare.Core/Views/Windows/StringConverterWindow.axaml b/src/OneWare.Core/Views/Windows/StringConverterWindow.axaml index 472892243..7a47f4c8f 100644 --- a/src/OneWare.Core/Views/Windows/StringConverterWindow.axaml +++ b/src/OneWare.Core/Views/Windows/StringConverterWindow.axaml @@ -1,4 +1,4 @@ - - - @@ -43,9 +34,8 @@ ShowLineNumbers="False" FontFamily="{DynamicResource EditorFont}" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" - Foreground="#E6E6E6" + Foreground="{DynamicResource ThemeEditorBrush}" Document="{Binding SourceText, Mode=TwoWay}" - FontWeight="Regular" Background="{DynamicResource ThemeControlLowBrush}" /> @@ -71,7 +61,7 @@ - + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + diff --git a/src/OneWare.ErrorList/Views/ErrorListView.axaml.cs b/src/OneWare.ErrorList/Views/ErrorListView.axaml.cs index 8c44705d4..6b97ed7d6 100644 --- a/src/OneWare.ErrorList/Views/ErrorListView.axaml.cs +++ b/src/OneWare.ErrorList/Views/ErrorListView.axaml.cs @@ -1,22 +1,47 @@ -using Avalonia.Controls; +using Avalonia; +using Avalonia.Controls; +using Avalonia.Controls.Primitives; +using Avalonia.Input; using Avalonia.Interactivity; +using Avalonia.VisualTree; using OneWare.ErrorList.ViewModels; namespace OneWare.ErrorList.Views; public partial class ErrorListView : UserControl { + private const double MinSearchWidth = 200; + public ErrorListView() { InitializeComponent(); - SearchErrors.Search += (o, i) => (DataContext as ErrorListViewModel)?.Filter(); + ProblemTree.AddHandler(DoubleTappedEvent, OnDoubleTapped, RoutingStrategies.Bubble); + ProblemTree.AddHandler(KeyDownEvent, OnKeyDown, RoutingStrategies.Tunnel); + HeaderGrid.SizeChanged += (_, _) => UpdateHeaderLayout(); + } + + private void UpdateHeaderLayout() + { + var wrap = HeaderGrid.Bounds.Width < FilterToolbar.DesiredSize.Width + MinSearchWidth; - ErrorList.AddHandler(DoubleTappedEvent, Open_Error, RoutingStrategies.Bubble); + Grid.SetRow(FilterToolbar, wrap ? 1 : 0); + Grid.SetColumn(FilterToolbar, wrap ? 0 : 1); + Grid.SetColumnSpan(FilterToolbar, wrap ? 2 : 1); + FilterToolbar.Margin = wrap ? new Thickness(0, 4, 0, 0) : new Thickness(6, 0, 0, 0); + } + + private void OnDoubleTapped(object? sender, TappedEventArgs e) + { + // Double clicking the expander chevron should only toggle the group + if (e.Source is Visual visual && visual.FindAncestorOfType(true) != null) return; + if (DataContext is ErrorListViewModel vm) _ = vm.GoToErrorAsync(); } - private void Open_Error(object? sender, RoutedEventArgs e) + private void OnKeyDown(object? sender, KeyEventArgs e) { - if (DataContext is ErrorListViewModel el) _ = el.GoToErrorAsync(); + if (e.Key != Key.Enter || DataContext is not ErrorListViewModel vm) return; + _ = vm.GoToErrorAsync(); + e.Handled = true; } -} \ No newline at end of file +} diff --git a/src/OneWare.Essentials/Controls/ComparisonControl.axaml b/src/OneWare.Essentials/Controls/ComparisonControl.axaml index da77e0fdc..d7708c746 100644 --- a/src/OneWare.Essentials/Controls/ComparisonControl.axaml +++ b/src/OneWare.Essentials/Controls/ComparisonControl.axaml @@ -38,7 +38,7 @@ - - - + - + diff --git a/src/OneWare.Essentials/Controls/FreqSelectorBox.axaml b/src/OneWare.Essentials/Controls/FreqSelectorBox.axaml index 10f7160a6..17b978261 100644 --- a/src/OneWare.Essentials/Controls/FreqSelectorBox.axaml +++ b/src/OneWare.Essentials/Controls/FreqSelectorBox.axaml @@ -6,7 +6,7 @@ x:Class="OneWare.Essentials.Controls.FreqSelectorBox"> *,50 - - + + + + + + + + - - - - - - - - + + + diff --git a/src/OneWare.Essentials/Controls/SearchBox.axaml.cs b/src/OneWare.Essentials/Controls/SearchBox.axaml.cs index a078832a1..2f3a8fa7e 100644 --- a/src/OneWare.Essentials/Controls/SearchBox.axaml.cs +++ b/src/OneWare.Essentials/Controls/SearchBox.axaml.cs @@ -20,6 +20,9 @@ public partial class SearchBox : UserControl public static readonly StyledProperty LabelProperty = AvaloniaProperty.Register(nameof(Label), "Search...", false, BindingMode.TwoWay); + public static readonly StyledProperty IsBusyProperty = + AvaloniaProperty.Register(nameof(IsBusy)); + public static readonly StyledProperty SearchTextProperty = AvaloniaProperty.Register(nameof(SearchText), "", false, BindingMode.TwoWay); @@ -52,6 +55,15 @@ public string Label set => SetValue(LabelProperty, value); } + /// + /// Shows a small spinner inside the field while a search is running. + /// + public bool IsBusy + { + get => GetValue(IsBusyProperty); + set => SetValue(IsBusyProperty, value); + } + public bool SearchButtonVisible { get => GetValue(SearchButtonVisibleProperty); diff --git a/src/OneWare.Essentials/Controls/SearchComboBox.axaml b/src/OneWare.Essentials/Controls/SearchComboBox.axaml index 1e1d7792c..e02a7c44f 100644 --- a/src/OneWare.Essentials/Controls/SearchComboBox.axaml +++ b/src/OneWare.Essentials/Controls/SearchComboBox.axaml @@ -1,4 +1,4 @@ - @@ -35,7 +35,7 @@ ContentTemplate="{TemplateBinding SelectionBoxItemTemplate}" /> - + BorderThickness="{DynamicResource ThemeBorderThickness}" + CornerRadius="{DynamicResource ThemeCornerRadius}"> - - + diff --git a/src/OneWare.Essentials/Controls/SegmentedControl.axaml b/src/OneWare.Essentials/Controls/SegmentedControl.axaml index da14375d4..dd256dba0 100644 --- a/src/OneWare.Essentials/Controls/SegmentedControl.axaml +++ b/src/OneWare.Essentials/Controls/SegmentedControl.axaml @@ -14,8 +14,9 @@ - + + - - + + @@ -58,7 +59,7 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/OneWare.Essentials/Styles/Controls/Buttons.axaml b/src/OneWare.Essentials/Styles/Controls/Buttons.axaml new file mode 100644 index 000000000..9f1d43a76 --- /dev/null +++ b/src/OneWare.Essentials/Styles/Controls/Buttons.axaml @@ -0,0 +1,249 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/OneWare.OssCadSuiteIntegration/Yosys/YosysService.cs b/src/OneWare.OssCadSuiteIntegration/Yosys/YosysService.cs index f864e26f9..20d508d57 100644 --- a/src/OneWare.OssCadSuiteIntegration/Yosys/YosysService.cs +++ b/src/OneWare.OssCadSuiteIntegration/Yosys/YosysService.cs @@ -1,4 +1,5 @@ using System.Text.Json; +using Avalonia.Controls; using Avalonia.Media; using Avalonia.Threading; using Microsoft.Extensions.Logging; @@ -62,11 +63,17 @@ public async Task CompileAsync(UniversalFpgaProjectRoot project, FpgaModel else outputService.WriteLine( $"==================\n\nCompilation failed in stage '{failedStage}' after {(int)compileTime.TotalMinutes:D2}:{compileTime.Seconds:D2}\n", - Brushes.Red); + GetThemeBrush("ErrorBrush")); return success; } + private static IBrush? GetThemeBrush(string key) + { + var app = Avalonia.Application.Current; + return app?.FindResource(app.RequestedThemeVariant, key) as IBrush; + } + public async Task SynthAsync(UniversalFpgaProjectRoot project, FpgaModel fpgaModel) { return await SynthAsync(project, fpgaModel, null); diff --git a/src/OneWare.Output/ViewModels/OutputBaseViewModel.cs b/src/OneWare.Output/ViewModels/OutputBaseViewModel.cs index c15221b06..267fddbb1 100644 --- a/src/OneWare.Output/ViewModels/OutputBaseViewModel.cs +++ b/src/OneWare.Output/ViewModels/OutputBaseViewModel.cs @@ -21,8 +21,12 @@ public abstract class OutputBaseViewModel : ExtendedTool private int _currentLineNumber = 1; + private bool _hasOutput; + private bool _isLoading; + private bool _wordWrap; + private TextDocument _outputDocument = new(); public OutputBaseViewModel(string iconKey) : base(iconKey) @@ -55,6 +59,18 @@ public bool AutoScroll set => SetProperty(ref _autoScroll, value); } + public bool WordWrap + { + get => _wordWrap; + set => SetProperty(ref _wordWrap, value); + } + + public bool HasOutput + { + get => _hasOutput; + private set => SetProperty(ref _hasOutput, value); + } + public void WriteLine(string text, IBrush? textColor = null, IProjectRoot? owner = null) { Write(text + "\n", textColor, owner); @@ -103,6 +119,7 @@ public void Write(string text, IBrush? textColor = null, IProjectRoot? owner = n } OutputDocument.EndUpdate(); + HasOutput = OutputDocument.TextLength > 0; } public void Clear() @@ -117,5 +134,7 @@ public void Clear() OutputDocument.UndoStack.ClearAll(); LineContexts.Clear(); _currentLineNumber = 1; + _currentLineLength = 0; + HasOutput = false; } } diff --git a/src/OneWare.Output/Views/OutputView.axaml b/src/OneWare.Output/Views/OutputView.axaml index 0310aaa2c..50525e2fe 100644 --- a/src/OneWare.Output/Views/OutputView.axaml +++ b/src/OneWare.Output/Views/OutputView.axaml @@ -9,48 +9,46 @@ Background="{DynamicResource ThemeControlLowBrush}" x:CompileBindings="True" x:DataType="viewModels:OutputViewModel"> - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + - \ No newline at end of file + diff --git a/src/OneWare.Output/Views/OutputView.axaml.cs b/src/OneWare.Output/Views/OutputView.axaml.cs index 4775e4c3c..cc6bf60d2 100644 --- a/src/OneWare.Output/Views/OutputView.axaml.cs +++ b/src/OneWare.Output/Views/OutputView.axaml.cs @@ -29,6 +29,16 @@ public OutputView() Output.Options.AllowScrollBelowDocument = false; Output.AddHandler(PointerPressedEvent, PointerPressedAfterCaretUpdate, RoutingStrategies.Bubble, true); + + FindButton.Click += (_, _) => + { + if (Output.SearchPanel is not { } searchPanel) return; + var selection = Output.SelectedText; + if (selection.Length > 0 && !selection.Contains('\n')) + searchPanel.SearchPattern = selection; + searchPanel.Open(); + searchPanel.Reactivate(); + }; } protected override void OnPointerMoved(PointerEventArgs e) diff --git a/src/OneWare.PackageManager/Services/PackageService.cs b/src/OneWare.PackageManager/Services/PackageService.cs index 961d372fb..8fa36bbf9 100644 --- a/src/OneWare.PackageManager/Services/PackageService.cs +++ b/src/OneWare.PackageManager/Services/PackageService.cs @@ -668,10 +668,13 @@ private void UpdateStatus(PackageState state) { if (state.Status == PackageStatus.NeedRestart) return; - var target = state.ResolveTargetVersion(true); + var hasInstalled = SemanticVersion.TryParse(state.InstalledVersion?.Version, out var installedVersion); + // Installed packages only see updates on their own channel: a newer prerelease is only an update + // when a prerelease is installed. Packages that are not installed stay available even if every + // version is a prerelease. + var target = state.ResolveTargetVersion(state.InstalledVersion == null); var hasTarget = SemanticVersion.TryParse(target?.Version, out var targetVersion); - var hasInstalled = SemanticVersion.TryParse(state.InstalledVersion?.Version, out var installedVersion); // An installed package stays removable even when its version string cannot be parsed or the // package disappeared from every repository, otherwise it can never be uninstalled again. diff --git a/src/OneWare.PackageManager/ViewModels/PackageManagerViewModel.cs b/src/OneWare.PackageManager/ViewModels/PackageManagerViewModel.cs index f8736e528..7789892c2 100644 --- a/src/OneWare.PackageManager/ViewModels/PackageManagerViewModel.cs +++ b/src/OneWare.PackageManager/ViewModels/PackageManagerViewModel.cs @@ -84,8 +84,7 @@ public PackageManagerViewModel(IPackageService packageService, IHttpService http _packageService.WhenValueChanged(x => x.IsUpdating) .Subscribe(x => Dispatcher.UIThread.Post(() => IsLoading = x)); - UpdateAllCommand = new AsyncRelayCommand(UpdateAllAsync, () => _packageService.Packages.Any(x => - x.Value.Status is PackageStatus.UpdateAvailable or PackageStatus.UpdateAvailablePrerelease)); + UpdateAllCommand = new AsyncRelayCommand(UpdateAllAsync, () => GetBulkUpdates().Count > 0); Observable.FromEventPattern(_packageService, nameof(_packageService.PackagesUpdated)).Subscribe(_ => { @@ -394,6 +393,7 @@ private PackageViewModel GetOrCreatePackageViewModel(string packageId, IPackageS private void OnPackageStatusChanged(object? sender, EventArgs e) { RefreshUpdateCount(); + UpdateAllCommand.NotifyCanExecuteChanged(); } /// @@ -487,14 +487,7 @@ private async Task AskForRestartAsync(Window? window) public async Task UpdateAllAsync() { - // A prerelease installation stays on the prerelease channel, so its updates are offered - // here too. - var packages = _packageService.Packages.Values - .Where(x => x.Status is PackageStatus.UpdateAvailable or PackageStatus.UpdateAvailablePrerelease) - .Select(x => (State: x, Target: x.ResolveTargetVersion())) - .Where(x => x.Target != null) - .OrderBy(x => x.State.Package.Name, StringComparer.OrdinalIgnoreCase) - .ToList(); + var packages = GetBulkUpdates(); if (packages.Count == 0) return true; @@ -506,7 +499,7 @@ public async Task UpdateAllAsync() { var installedVersion = x.State.InstalledVersion?.Version ?? "?"; - return $"- **{x.State.Package.Name}** `{installedVersion} -> {x.Target!.Version}`"; + return $"- **{x.State.Package.Name}** `{installedVersion} -> {x.Target.Version}`"; }); var message = @@ -552,6 +545,21 @@ public async Task UpdateAllAsync() return true; } + /// + /// Packages "Update All" applies, on the channel each package is installed from: stable installations + /// only move to newer stable versions, prerelease installations also to newer prereleases. + /// + private List<(IPackageState State, PackageVersion Target)> GetBulkUpdates() + { + return _packageService.Packages.Values + .Where(x => x.Status is PackageStatus.UpdateAvailable or PackageStatus.UpdateAvailablePrerelease) + .Select(x => (State: x, Target: x.ResolveUpdateVersion())) + .Where(x => x.Target != null) + .Select(x => (x.State, x.Target!)) + .OrderBy(x => x.State.Package.Name, StringComparer.OrdinalIgnoreCase) + .ToList(); + } + private static string[] SplitCategoryPath(string? categoryPath) { if (string.IsNullOrWhiteSpace(categoryPath)) return []; diff --git a/src/OneWare.PackageManager/ViewModels/PackageViewModel.cs b/src/OneWare.PackageManager/ViewModels/PackageViewModel.cs index d813e4723..e772bfb45 100644 --- a/src/OneWare.PackageManager/ViewModels/PackageViewModel.cs +++ b/src/OneWare.PackageManager/ViewModels/PackageViewModel.cs @@ -64,7 +64,8 @@ public PackageViewModel(IPackageState packageState, IPackageService packageServi UpdateCommand = new AsyncRelayCommand(_ => _packageService.UpdateAsync(PackageState.Package.Id!, SelectedVersionModel!.Version), - _ => PackageState.Status is PackageStatus.UpdateAvailable or PackageStatus.UpdateAvailablePrerelease); + _ => PackageState.Status is PackageStatus.UpdateAvailable or PackageStatus.UpdateAvailablePrerelease + or PackageStatus.Installed); CancelCommand = new RelayCommand(() => _packageService.CancelInstall(PackageState.Package.Id!), () => PackageState.Status is PackageStatus.Installing); @@ -223,6 +224,8 @@ private void UpdateStatus() break; case PackageStatus.UpdateAvailable when sV > iV: case PackageStatus.UpdateAvailablePrerelease when sV > iV: + // No update is offered, but the user explicitly picked a newer version (e.g. a prerelease) + case PackageStatus.Installed when iV != null && sV > iV: PrimaryButtonText = "Update"; primaryButtonBrushObservable = Application.Current!.GetResourceObservable("ThemeAccentBrush"); MainButtonCommand = UpdateCommand; diff --git a/src/OneWare.PackageManager/Views/ConfigurationProfileImportView.axaml b/src/OneWare.PackageManager/Views/ConfigurationProfileImportView.axaml index 015cf1515..63d8966f9 100644 --- a/src/OneWare.PackageManager/Views/ConfigurationProfileImportView.axaml +++ b/src/OneWare.PackageManager/Views/ConfigurationProfileImportView.axaml @@ -5,7 +5,7 @@ xmlns:viewModels="clr-namespace:OneWare.PackageManager.ViewModels" xmlns:models="clr-namespace:OneWare.PackageManager.Models" xmlns:controls="clr-namespace:OneWare.Essentials.Controls;assembly=OneWare.Essentials" - PrefWidth="450" PrefHeight="280" Padding="4" + PrefWidth="450" PrefHeight="280" Padding="{DynamicResource ThemePaddingSmall}" mc:Ignorable="d" x:Class="OneWare.PackageManager.Views.ConfigurationProfileImportView" CustomIcon="{DynamicResource PackageManager}" @@ -47,12 +47,11 @@ - + @@ -69,11 +68,11 @@ - diff --git a/src/OneWare.PackageManager/Views/PackageManagerView.axaml b/src/OneWare.PackageManager/Views/PackageManagerView.axaml index 06b162945..693865673 100644 --- a/src/OneWare.PackageManager/Views/PackageManagerView.axaml +++ b/src/OneWare.PackageManager/Views/PackageManagerView.axaml @@ -1,4 +1,4 @@ - @@ -33,68 +32,59 @@ - - - - - - - - - - + - + + - - - - - + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + - @@ -72,7 +73,7 @@ - @@ -86,7 +87,7 @@ - + @@ -100,15 +101,13 @@ - + Classes.InvalidBorder="{Binding !SelectedVersionModel.CompatibilityReport.IsCompatible, FallbackValue={x:False}}"> - + @@ -131,10 +130,9 @@ - @@ -149,7 +147,7 @@ - + diff --git a/src/OneWare.ProjectExplorer/Views/ProjectExplorerMainWindowToolBarExtension.axaml b/src/OneWare.ProjectExplorer/Views/ProjectExplorerMainWindowToolBarExtension.axaml index de4b311c6..3b95d794d 100644 --- a/src/OneWare.ProjectExplorer/Views/ProjectExplorerMainWindowToolBarExtension.axaml +++ b/src/OneWare.ProjectExplorer/Views/ProjectExplorerMainWindowToolBarExtension.axaml @@ -11,14 +11,13 @@ x:Class="OneWare.ProjectExplorer.Views.ProjectExplorerMainWindowToolBarExtension" x:DataType="viewModels:ProjectExplorerViewModel"> - - + diff --git a/src/OneWare.ProjectExplorer/Views/ProjectExplorerTheme.axaml b/src/OneWare.ProjectExplorer/Views/ProjectExplorerTheme.axaml index ea6ad6003..891239328 100644 --- a/src/OneWare.ProjectExplorer/Views/ProjectExplorerTheme.axaml +++ b/src/OneWare.ProjectExplorer/Views/ProjectExplorerTheme.axaml @@ -1,348 +1,5 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - M1875 1011l-787 787v-1798h-128v1798l-787 -787l-90 90l941 941l941 -941z - M1965 947l-941 -941l-941 941l90 90l787 -787v1798h128v-1798l787 787z - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file + + diff --git a/src/OneWare.ProjectExplorer/Views/ProjectExplorerView.axaml b/src/OneWare.ProjectExplorer/Views/ProjectExplorerView.axaml index 326c834d2..5a7fef983 100644 --- a/src/OneWare.ProjectExplorer/Views/ProjectExplorerView.axaml +++ b/src/OneWare.ProjectExplorer/Views/ProjectExplorerView.axaml @@ -12,11 +12,8 @@ mc:Ignorable="d" d:DesignWidth="300" d:DesignHeight="450" x:Class="OneWare.ProjectExplorer.Views.ProjectExplorerView" Background="{DynamicResource ThemeControlLowBrush}" - BorderBrush="{DynamicResource ThemeBorderLowBrush}" x:CompileBindings="True" x:DataType="viewModels:ProjectExplorerViewModel" - Name="ProjectExplorerViewView" - Foreground="{DynamicResource ThemeForegroundColor}" - BorderThickness="0"> + Name="ProjectExplorerViewView"> @@ -25,14 +22,10 @@ - @@ -43,10 +36,8 @@ - - + + - + @@ -79,11 +70,13 @@ - + + + + diff --git a/src/OneWare.ProjectSystem/Models/ProjectEntry.cs b/src/OneWare.ProjectSystem/Models/ProjectEntry.cs index be09ac0ac..4234cc00d 100644 --- a/src/OneWare.ProjectSystem/Models/ProjectEntry.cs +++ b/src/OneWare.ProjectSystem/Models/ProjectEntry.cs @@ -11,6 +11,7 @@ namespace OneWare.ProjectSystem.Models; public abstract class ProjectEntry : ObservableObject, IProjectEntry { + private readonly Dictionary _tags = new(); private string _name; protected ProjectEntry(string name, IProjectFolder? topFolder) @@ -43,6 +44,24 @@ public float TextOpacity set => SetProperty(ref field, value); } = 1f; + public IReadOnlyList Tags + { + get; + private set => SetProperty(ref field, value); + } = []; + + public void AddTag(string key, ProjectExplorerTag tag) + { + if (_tags.TryGetValue(key, out var existing) && existing == tag) return; + _tags[key] = tag; + Tags = _tags.Values.ToArray(); + } + + public void RemoveTag(string key) + { + if (_tags.Remove(key)) Tags = _tags.Values.ToArray(); + } + public IconModel? Icon { get; diff --git a/src/OneWare.Python/PythonInterpreterPickerService.cs b/src/OneWare.Python/PythonInterpreterPickerService.cs index 1d319082e..b62da3d3e 100644 --- a/src/OneWare.Python/PythonInterpreterPickerService.cs +++ b/src/OneWare.Python/PythonInterpreterPickerService.cs @@ -3,7 +3,6 @@ using OneWare.Essentials.Commands; using OneWare.Essentials.Enums; using OneWare.Essentials.Helpers; -using OneWare.Essentials.Models; using OneWare.Essentials.Services; namespace OneWare.Python; @@ -33,19 +32,6 @@ public void Initialize() { Detail = "Select the Python interpreter for the active workspace" }); - windows.RegisterMenuItem("MainWindow_MainMenu/Code", - new MenuItemModel("Python") - { - Header = "Python", - Items = - [ - new MenuItemModel("SelectInterpreter") - { - Header = "Select Interpreter...", - Command = command - } - ] - }); } public string? GetActiveWorkspace() diff --git a/src/OneWare.SearchList/Views/SearchListView.axaml b/src/OneWare.SearchList/Views/SearchListView.axaml index 4bedc5d47..7317fa5cc 100644 --- a/src/OneWare.SearchList/Views/SearchListView.axaml +++ b/src/OneWare.SearchList/Views/SearchListView.axaml @@ -1,4 +1,4 @@ - - + @@ -20,26 +20,35 @@ - + - + - + - + @@ -54,27 +63,20 @@ - - - - - - - - - - - - - + + + + + + + - @@ -91,14 +93,14 @@ - - - @@ -119,7 +120,7 @@ + Classes="mono"> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Padding="6 4" /> - + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + @@ -93,4 +123,4 @@ - \ No newline at end of file + diff --git a/src/OneWare.SerialMonitor/Views/SerialMonitorView.axaml.cs b/src/OneWare.SerialMonitor/Views/SerialMonitorView.axaml.cs index 1c50c90a7..68aba562e 100644 --- a/src/OneWare.SerialMonitor/Views/SerialMonitorView.axaml.cs +++ b/src/OneWare.SerialMonitor/Views/SerialMonitorView.axaml.cs @@ -19,5 +19,15 @@ public SerialMonitorView() else if (args.Key == Key.Down) vm.InsertDown(); } }, RoutingStrategies.Direct); + + FindButton.Click += (_, _) => + { + if (Output.SearchPanel is not { } searchPanel) return; + var selection = Output.SelectedText; + if (selection.Length > 0 && !selection.Contains('\n')) + searchPanel.SearchPattern = selection; + searchPanel.Open(); + searchPanel.Reactivate(); + }; } } \ No newline at end of file diff --git a/src/OneWare.Settings/ViewModels/SettingTypes/SliderSettingViewModel.cs b/src/OneWare.Settings/ViewModels/SettingTypes/SliderSettingViewModel.cs index 44d3d5d5e..2c6236940 100644 --- a/src/OneWare.Settings/ViewModels/SettingTypes/SliderSettingViewModel.cs +++ b/src/OneWare.Settings/ViewModels/SettingTypes/SliderSettingViewModel.cs @@ -1,31 +1,70 @@ -using DynamicData.Binding; +using System.Globalization; +using DynamicData.Binding; using OneWare.Essentials.Models; namespace OneWare.Settings.ViewModels.SettingTypes; public class SliderSettingViewModel : TitledSettingViewModel { + // Text as typed by the user; kept while editing so e.g. "60." is not rewritten to "60" (which moves the caret) + private string? _editText; + private bool _isUpdatingFromText; + public SliderSettingViewModel(SliderSetting setting) : base(setting) { Setting = setting; - setting.WhenValueChanged(x => x.Value).Subscribe(x => { OnPropertyChanged(nameof(TextBoxValue)); }); + setting.WhenValueChanged(x => x.Value).Subscribe(_ => + { + if (_isUpdatingFromText) return; + _editText = null; + OnPropertyChanged(nameof(TextBoxValue)); + }); } public new SliderSetting Setting { get; } - public double TextBoxValue + public string TextBoxValue { - get => Math.Round((double)Setting.Value, GetPrecision(Setting.Step)); + get => _editText ?? FormatValue((double)Setting.Value); set { - if (value < Setting.Min || value > Setting.Max) - throw new ArgumentException(); + if (!double.TryParse(value, NumberStyles.Float, CultureInfo.CurrentCulture, out var parsed)) + throw new FormatException("Please enter a valid number"); + + if (parsed < Setting.Min || parsed > Setting.Max) + throw new ArgumentException($"Value must be between {FormatValue(Setting.Min)} and {FormatValue(Setting.Max)}"); - Setting.Value = Math.Round(value, GetPrecision(Setting.Step)); + var rounded = Math.Round(parsed, GetPrecision(Setting.Step)); + + _editText = value; + _isUpdatingFromText = true; + try + { + Setting.Value = rounded; + } + finally + { + _isUpdatingFromText = false; + } } } + /// + /// Replaces the typed text with the formatted setting value (call when editing ends). + /// + public void CommitText() + { + if (_editText == null) return; + _editText = null; + OnPropertyChanged(nameof(TextBoxValue)); + } + + private string FormatValue(double value) + { + return Math.Round(value, GetPrecision(Setting.Step)).ToString(CultureInfo.CurrentCulture); + } + // Helper method: how many decimals do we need? private int GetPrecision(double step) { diff --git a/src/OneWare.Settings/Views/ApplicationSettingsView.axaml b/src/OneWare.Settings/Views/ApplicationSettingsView.axaml index 16fceac86..3173f2210 100644 --- a/src/OneWare.Settings/Views/ApplicationSettingsView.axaml +++ b/src/OneWare.Settings/Views/ApplicationSettingsView.axaml @@ -22,11 +22,6 @@ - - @@ -41,20 +36,20 @@ - - + - + - @@ -68,10 +63,10 @@ - + - + @@ -79,7 +74,7 @@ - @@ -97,21 +92,21 @@ diff --git a/src/OneWare.Settings/Views/SettingTypes/AdvancedComboBoxSearchSettingView.axaml b/src/OneWare.Settings/Views/SettingTypes/AdvancedComboBoxSearchSettingView.axaml index 251137b97..400ab6835 100644 --- a/src/OneWare.Settings/Views/SettingTypes/AdvancedComboBoxSearchSettingView.axaml +++ b/src/OneWare.Settings/Views/SettingTypes/AdvancedComboBoxSearchSettingView.axaml @@ -15,14 +15,13 @@ - - - diff --git a/src/OneWare.Settings/Views/SettingTypes/AdvancedComboBoxSettingView.axaml b/src/OneWare.Settings/Views/SettingTypes/AdvancedComboBoxSettingView.axaml index 9c2dce6fd..7920a716e 100644 --- a/src/OneWare.Settings/Views/SettingTypes/AdvancedComboBoxSettingView.axaml +++ b/src/OneWare.Settings/Views/SettingTypes/AdvancedComboBoxSettingView.axaml @@ -15,14 +15,13 @@ - - - diff --git a/src/OneWare.Settings/Views/SettingTypes/CheckBoxSettingView.axaml b/src/OneWare.Settings/Views/SettingTypes/CheckBoxSettingView.axaml index 46e240345..79f799865 100644 --- a/src/OneWare.Settings/Views/SettingTypes/CheckBoxSettingView.axaml +++ b/src/OneWare.Settings/Views/SettingTypes/CheckBoxSettingView.axaml @@ -1,4 +1,4 @@ - - - - + \ No newline at end of file diff --git a/src/OneWare.Settings/Views/SettingTypes/ColorPickerSettingView.axaml b/src/OneWare.Settings/Views/SettingTypes/ColorPickerSettingView.axaml index 183d6efea..d62178ac2 100644 --- a/src/OneWare.Settings/Views/SettingTypes/ColorPickerSettingView.axaml +++ b/src/OneWare.Settings/Views/SettingTypes/ColorPickerSettingView.axaml @@ -1,4 +1,4 @@ - - - - - + + - + - - - - @@ -64,10 +62,8 @@ SelectedIndex="{Binding SelectedListIndex, Mode=TwoWay}" IsEnabled="{Binding Setting.IsEnabledObservable^, FallbackValue={x:True}}" Height="100" - BorderBrush="{DynamicResource ThemeBorderLowBrush}" BorderThickness="0 1 0 0" /> - diff --git a/src/OneWare.Settings/Views/SettingTypes/ListBoxSettingView.axaml b/src/OneWare.Settings/Views/SettingTypes/ListBoxSettingView.axaml index ae7b61aa9..603ca0721 100644 --- a/src/OneWare.Settings/Views/SettingTypes/ListBoxSettingView.axaml +++ b/src/OneWare.Settings/Views/SettingTypes/ListBoxSettingView.axaml @@ -1,4 +1,4 @@ - - - - - - - - - diff --git a/src/OneWare.Settings/Views/SettingTypes/PathSettingView.axaml b/src/OneWare.Settings/Views/SettingTypes/PathSettingView.axaml index e29e86502..1721b9f54 100644 --- a/src/OneWare.Settings/Views/SettingTypes/PathSettingView.axaml +++ b/src/OneWare.Settings/Views/SettingTypes/PathSettingView.axaml @@ -1,4 +1,4 @@ - - - - - - - - - - - - - - - - - - - - - + + + \ No newline at end of file diff --git a/src/OneWare.Settings/Views/SettingTypes/SliderSettingView.axaml b/src/OneWare.Settings/Views/SettingTypes/SliderSettingView.axaml index 815ab8306..ecd8b3273 100644 --- a/src/OneWare.Settings/Views/SettingTypes/SliderSettingView.axaml +++ b/src/OneWare.Settings/Views/SettingTypes/SliderSettingView.axaml @@ -1,4 +1,4 @@ - - + - - - - - - - - - + - - + + + + + + + \ No newline at end of file diff --git a/src/OneWare.Settings/Views/SettingTypes/SliderSettingView.axaml.cs b/src/OneWare.Settings/Views/SettingTypes/SliderSettingView.axaml.cs index a61ce5d29..b9a2cc088 100644 --- a/src/OneWare.Settings/Views/SettingTypes/SliderSettingView.axaml.cs +++ b/src/OneWare.Settings/Views/SettingTypes/SliderSettingView.axaml.cs @@ -1,4 +1,7 @@ using Avalonia.Controls; +using Avalonia.Input; +using Avalonia.Interactivity; +using OneWare.Settings.ViewModels.SettingTypes; namespace OneWare.Settings.Views.SettingTypes; @@ -8,4 +11,14 @@ public SliderSettingView() { InitializeComponent(); } + + private void ValueTextBox_OnLostFocus(object? sender, RoutedEventArgs e) + { + (DataContext as SliderSettingViewModel)?.CommitText(); + } + + private void ValueTextBox_OnKeyDown(object? sender, KeyEventArgs e) + { + if (e.Key == Key.Enter) (DataContext as SliderSettingViewModel)?.CommitText(); + } } \ No newline at end of file diff --git a/src/OneWare.Settings/Views/SettingTypes/TextBoxSettingView.axaml b/src/OneWare.Settings/Views/SettingTypes/TextBoxSettingView.axaml index 8b762d2fb..5ed5ca528 100644 --- a/src/OneWare.Settings/Views/SettingTypes/TextBoxSettingView.axaml +++ b/src/OneWare.Settings/Views/SettingTypes/TextBoxSettingView.axaml @@ -1,4 +1,4 @@ - - - - - + @@ -192,7 +265,13 @@ - + + + + + - + - + @@ -245,8 +330,8 @@ - - + @@ -265,11 +350,11 @@ @@ -286,7 +371,7 @@ Command="{Binding #SourceControlViewView.((viewModels:SourceControlViewModel)DataContext).StageCommand, FallbackValue={x:Null}}" CommandParameter="{Binding Status.FilePath}"> - + @@ -319,15 +404,15 @@ - - + @@ -351,11 +436,11 @@ @@ -406,8 +491,8 @@ - - + - + @@ -438,16 +523,16 @@ @@ -485,7 +570,7 @@ Command="{Binding #SourceControlViewView.((viewModels:SourceControlViewModel)DataContext).DiscardAsync}" CommandParameter="{Binding Status.FilePath}"> - + @@ -501,24 +586,23 @@ - - + BorderThickness="0 1 0 0" + CornerRadius="0 0 3 3" Padding="6 4"> @@ -540,13 +624,152 @@ IsVisible="{Binding !ActiveRepository.StagedChanges.Count, FallbackValue={x:False}}" ToolTip.Tip="Stage all changes and commit"> - + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - \ No newline at end of file + diff --git a/src/OneWare.SourceControl/Views/SourceControlView.axaml.cs b/src/OneWare.SourceControl/Views/SourceControlView.axaml.cs index e55e5deef..3fc35033d 100644 --- a/src/OneWare.SourceControl/Views/SourceControlView.axaml.cs +++ b/src/OneWare.SourceControl/Views/SourceControlView.axaml.cs @@ -1,5 +1,8 @@ -using Avalonia.Controls; +using Avalonia; +using Avalonia.Controls; +using Avalonia.Input; using Avalonia.Interactivity; +using Avalonia.VisualTree; using LibGit2Sharp; using OneWare.SourceControl.Models; using OneWare.SourceControl.ViewModels; @@ -8,6 +11,9 @@ namespace OneWare.SourceControl.Views; public partial class SourceControlView : UserControl { + private const int GraphRow = 3; + private GridLength _graphHeight = new(1, GridUnitType.Star); + public SourceControlView() { InitializeComponent(); @@ -15,6 +21,78 @@ public SourceControlView() ChangeListBox.DoubleTapped += OnChangeDoubleTap; StagedChangeListBox.DoubleTapped += OnStagedChangeDoubleTap; MergeChangeListBox.DoubleTapped += OnMergeChangeDoubleTap; + + GraphListBox.AddHandler(TappedEvent, OnGraphTapped, RoutingStrategies.Bubble, true); + GraphListBox.AddHandler(KeyDownEvent, OnGraphKeyDown, RoutingStrategies.Bubble, true); + GraphListBox.TemplateApplied += (_, args) => + { + if (args.NameScope.Find("PART_ScrollViewer") is { } scrollViewer) + scrollViewer.ScrollChanged += OnGraphScrollChanged; + }; + + GraphSection.PropertyChanged += (_, args) => + { + if (args.Property == Expander.IsExpandedProperty || args.Property == IsVisibleProperty) + UpdateGraphRow(); + }; + UpdateGraphRow(); + } + + protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e) + { + base.OnAttachedToVisualTree(e); + (DataContext as SourceControlViewModel)?.AttachTopLevel(TopLevel.GetTopLevel(this)); + } + + /// + /// The graph shares the height with the changes only while it is expanded, the splitter keeps its size. + /// + private void UpdateGraphRow() + { + var row = RootGrid.RowDefinitions[GraphRow]; + var expanded = GraphSection.IsVisible && GraphSection.IsExpanded; + + if (!row.Height.IsAuto) _graphHeight = row.Height; + row.Height = expanded ? _graphHeight : GridLength.Auto; + row.MinHeight = expanded ? 80 : 0; + } + + private void OnGraphScrollChanged(object? sender, ScrollChangedEventArgs e) + { + if (sender is not ScrollViewer scrollViewer || DataContext is not SourceControlViewModel vm) return; + if (scrollViewer.Offset.Y + scrollViewer.Viewport.Height >= scrollViewer.Extent.Height - 200) + _ = vm.LoadMoreHistoryAsync(); + } + + private void OnGraphTapped(object? sender, TappedEventArgs e) + { + if (DataContext is not SourceControlViewModel vm) return; + + foreach (var visual in (e.Source as Visual)?.GetSelfAndVisualAncestors() ?? []) + { + if (visual == GraphListBox || visual is Button) return; + if (visual is not Control control) continue; + + if (control.Classes.Contains("CommitFile") && control.DataContext is GitCommitFileModel file) + { + vm.CompareCommitFile(file); + return; + } + + if (control.Classes.Contains("CommitHeader") && control.DataContext is GitGraphRow row) + { + vm.ToggleCommit(row); + return; + } + } + } + + private void OnGraphKeyDown(object? sender, KeyEventArgs e) + { + if (e.Key is not (Key.Enter or Key.Space) || DataContext is not SourceControlViewModel vm || + GraphListBox.SelectedItem is not GitGraphRow row) return; + vm.ToggleCommit(row); + e.Handled = true; } public void OnChangeDoubleTap(object? sender, RoutedEventArgs e) @@ -39,4 +117,4 @@ public async Task AutoOpenAsync(SourceControlFileModel scm) if (scm.Status.State.HasFlag(FileStatus.Conflicted)) await vm.OpenFileAsync(scm.Status.FilePath); else vm.CompareAndSwitch(scm.Status.FilePath); } -} \ No newline at end of file +} diff --git a/src/OneWare.TerminalManager/Views/TerminalManagerView.axaml b/src/OneWare.TerminalManager/Views/TerminalManagerView.axaml index 35debaeac..0eae4f79a 100644 --- a/src/OneWare.TerminalManager/Views/TerminalManagerView.axaml +++ b/src/OneWare.TerminalManager/Views/TerminalManagerView.axaml @@ -25,9 +25,9 @@ - - + - diff --git a/src/OneWare.TypeScript/TypeScriptModule.cs b/src/OneWare.TypeScript/TypeScriptModule.cs index bc90c43f4..e7217228e 100644 --- a/src/OneWare.TypeScript/TypeScriptModule.cs +++ b/src/OneWare.TypeScript/TypeScriptModule.cs @@ -168,7 +168,7 @@ public override void Initialize(IServiceProvider serviceProvider) languageManager.RegisterService(typeof(LanguageServiceTypeScript), true, SupportedExtensions); var fileIconService = serviceProvider.Resolve(); - fileIconService.RegisterFileIcon("SimpleIcons.TypeScript", ".ts", ".tsx", ".mts", ".cts"); - fileIconService.RegisterFileIcon("Ionicons.LogoJavascript", ".jsx", ".mjs", ".cjs"); + fileIconService.RegisterFileIcon("FileIcon.TypeScript", ".ts", ".tsx", ".mts", ".cts"); + fileIconService.RegisterFileIcon("FileIcon.JavaScript", ".jsx", ".mjs", ".cjs"); } } diff --git a/src/OneWare.UniversalFpgaProjectSystem/Models/UniversalFpgaProjectRoot.cs b/src/OneWare.UniversalFpgaProjectSystem/Models/UniversalFpgaProjectRoot.cs index 82aa280eb..604768245 100644 --- a/src/OneWare.UniversalFpgaProjectSystem/Models/UniversalFpgaProjectRoot.cs +++ b/src/OneWare.UniversalFpgaProjectSystem/Models/UniversalFpgaProjectRoot.cs @@ -27,11 +27,12 @@ public UniversalFpgaProjectRoot(string projectFilePath) : base(projectFilePath) { if (x is IProjectFile file && IsTestBench(file.RelativePath)) { - x.Icon?.AddOverlay("TestBench", "TestBenchOverlay"); + x.AddTag("TestBench", new ProjectExplorerTag("Testbench", ProjectExplorerTagKind.Success, + "Simulation testbench")); } else { - x.Icon?.RemoveOverlay("TestBench"); + x.RemoveTag("TestBench"); } }); @@ -40,11 +41,12 @@ public UniversalFpgaProjectRoot(string projectFilePath) : base(projectFilePath) if (x is IProjectFile file && TopEntityFilePath != null && file.RelativePath.EqualPaths(TopEntityFilePath)) { - x.Icon?.AddOverlay("TopEntity", "VsImageLib2019.DownloadOverlay16X"); + x.AddTag("TopEntity", new ProjectExplorerTag("Top", ProjectExplorerTagKind.Accent, + "Top entity of the design")); } else { - x.Icon?.RemoveOverlay("TopEntity"); + x.RemoveTag("TopEntity"); } }); diff --git a/src/OneWare.UniversalFpgaProjectSystem/Views/GenericFpgaView.axaml b/src/OneWare.UniversalFpgaProjectSystem/Views/GenericFpgaView.axaml index d3e0ab88c..ecea13018 100644 --- a/src/OneWare.UniversalFpgaProjectSystem/Views/GenericFpgaView.axaml +++ b/src/OneWare.UniversalFpgaProjectSystem/Views/GenericFpgaView.axaml @@ -11,7 +11,7 @@ ClipToBounds="False"> diff --git a/src/OneWare.UniversalFpgaProjectSystem/Views/UniversalFpgaProjectPinPlannerView.axaml b/src/OneWare.UniversalFpgaProjectSystem/Views/UniversalFpgaProjectPinPlannerView.axaml index 3f921d48b..abddaa0fb 100644 --- a/src/OneWare.UniversalFpgaProjectSystem/Views/UniversalFpgaProjectPinPlannerView.axaml +++ b/src/OneWare.UniversalFpgaProjectSystem/Views/UniversalFpgaProjectPinPlannerView.axaml @@ -1,4 +1,4 @@ - - + Padding="32" MinWidth="360"> - + Classes="muted" TextWrapping="Wrap" HorizontalAlignment="Center" TextAlignment="Center" /> + HorizontalAlignment="Stretch" /> - + @@ -60,7 +58,7 @@ - + + Width="200" /> @@ -102,13 +100,14 @@ @@ -162,10 +161,9 @@ - + SearchButtonVisible="False"> @@ -193,10 +191,11 @@ IsVisible="{Binding ConnectedNode, Converter={x:Static ObjectConverters.IsNotNull}}" Height="20" Width="20" ToolTip.Tip="Disconnect"> - + VerticalAlignment="Center" + Width="10" /> - - + VerticalAlignment="Center" + Width="10" /> - - @@ -31,12 +26,12 @@ - - + diff --git a/src/OneWare.UniversalFpgaProjectSystem/Views/UniversalFpgaProjectTestBenchToolBarView.axaml b/src/OneWare.UniversalFpgaProjectSystem/Views/UniversalFpgaProjectTestBenchToolBarView.axaml index 91dfbc27a..5dbc5b3ad 100644 --- a/src/OneWare.UniversalFpgaProjectSystem/Views/UniversalFpgaProjectTestBenchToolBarView.axaml +++ b/src/OneWare.UniversalFpgaProjectSystem/Views/UniversalFpgaProjectTestBenchToolBarView.axaml @@ -4,45 +4,36 @@ xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:viewModels="clr-namespace:OneWare.UniversalFpgaProjectSystem.ViewModels" xmlns:controls="clr-namespace:OneWare.Essentials.Controls;assembly=OneWare.Essentials" - mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" + mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="40" x:Class="OneWare.UniversalFpgaProjectSystem.Views.UniversalFpgaProjectTestBenchToolBarView" x:DataType="viewModels:UniversalFpgaProjectTestBenchToolBarViewModel" IsVisible="{Binding IsVisible}"> - - - - + + + + + + + + + + - - - - - - - + + - - - - - - - - - + - \ No newline at end of file + diff --git a/src/OneWare.UniversalFpgaProjectSystem/Views/UniversalFpgaProjectToolBarView.axaml b/src/OneWare.UniversalFpgaProjectSystem/Views/UniversalFpgaProjectToolBarView.axaml index 42ca2f175..8166201d2 100644 --- a/src/OneWare.UniversalFpgaProjectSystem/Views/UniversalFpgaProjectToolBarView.axaml +++ b/src/OneWare.UniversalFpgaProjectSystem/Views/UniversalFpgaProjectToolBarView.axaml @@ -1,4 +1,4 @@ - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/OneWare.Updater/ViewModels/UpdaterViewModel.cs b/src/OneWare.Updater/ViewModels/UpdaterViewModel.cs index 6981e47c1..6aed79309 100644 --- a/src/OneWare.Updater/ViewModels/UpdaterViewModel.cs +++ b/src/OneWare.Updater/ViewModels/UpdaterViewModel.cs @@ -198,7 +198,7 @@ public async Task DownloadUpdateAsync(Control owner) var updatablePackages = _packageService.Packages .Select(x => x.Value) .Where(x => x.Status is PackageStatus.UpdateAvailable or PackageStatus.UpdateAvailablePrerelease) - .Select(x => (State: x, Target: x.ResolveTargetVersion())) + .Select(x => (State: x, Target: x.ResolveUpdateVersion())) .Where(x => x.Target != null) .ToArray(); diff --git a/src/OneWare.Updater/Views/UpdaterView.axaml b/src/OneWare.Updater/Views/UpdaterView.axaml index 92cc51166..0b5ea1a17 100644 --- a/src/OneWare.Updater/Views/UpdaterView.axaml +++ b/src/OneWare.Updater/Views/UpdaterView.axaml @@ -5,7 +5,7 @@ xmlns:controls="clr-namespace:OneWare.Essentials.Controls;assembly=OneWare.Essentials" xmlns:viewModels="clr-namespace:OneWare.Updater.ViewModels" xmlns:converters="clr-namespace:OneWare.Essentials.Converters;assembly=OneWare.Essentials" - PrefWidth="400" SizeToContent="Height" Padding="8" + PrefWidth="400" SizeToContent="Height" Padding="{DynamicResource ThemePadding}" mc:Ignorable="d" x:Class="OneWare.Updater.Views.UpdaterView" CustomIcon="{DynamicResource VsImageLib.DownloadDefault16X}" @@ -19,8 +19,8 @@ - - @@ -84,21 +84,25 @@ - - - - - + diff --git a/src/OneWare.Verilog/Views/VerilogOutlineView.axaml b/src/OneWare.Verilog/Views/VerilogOutlineView.axaml index a4ed0c6aa..d94f00203 100644 --- a/src/OneWare.Verilog/Views/VerilogOutlineView.axaml +++ b/src/OneWare.Verilog/Views/VerilogOutlineView.axaml @@ -8,7 +8,7 @@ x:DataType="viewModels:VerilogOutlineViewModel" mc:Ignorable="d" d:DesignWidth="300" d:DesignHeight="500"> - + @@ -18,7 +18,7 @@ - + diff --git a/src/OneWare.Verilog/Views/VerilogRtlTreeView.axaml b/src/OneWare.Verilog/Views/VerilogRtlTreeView.axaml index 5a2fdee6d..ccdf67ff4 100644 --- a/src/OneWare.Verilog/Views/VerilogRtlTreeView.axaml +++ b/src/OneWare.Verilog/Views/VerilogRtlTreeView.axaml @@ -8,7 +8,7 @@ x:DataType="viewModels:VerilogRtlTreeViewModel" mc:Ignorable="d" d:DesignWidth="320" d:DesignHeight="500"> - + @@ -165,7 +161,7 @@ Bounds="{Binding #SimulatorScaleRenderer.TextAreaBounds, Mode=OneWayToSource}" ItemsSource="{Binding Signals}" SelectedItem="{Binding SelectedSignal}" Name="TextPartScroll" - Classes="Scroll" Padding="1 0 0 0"> + Classes="Scroll transparent" Padding="1 0 0 0"> @@ -214,7 +210,7 @@ ScrollViewer.HorizontalScrollBarVisibility="Disabled" ScrollViewer.VerticalScrollBarVisibility="Visible" ItemsSource="{Binding Signals}" SelectedItem="{Binding SelectedSignal}" - Classes="Scroll"> + Classes="Scroll transparent" Padding="0">