diff --git a/.gitignore b/.gitignore index 04727ec..5da82ee 100644 --- a/.gitignore +++ b/.gitignore @@ -217,3 +217,4 @@ llms/ # wwwroot/ smoothui/ +*Settings.user \ No newline at end of file diff --git a/AGENTS.md b/AGENTS.md index 4bd3ed3..c208bbb 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,71 +1,246 @@ -# Agent Rules - -## Avalonia Clipboard Usage - -### Image Clipboard (Windows-only) - -Avalonia's built-in clipboard API (`DataObject`, `SetDataObjectAsync`) is deprecated and doesn't reliably copy images. For Windows, use **WinForms interop**: - -```csharp -using System.Runtime.Versioning; - -[SupportedOSPlatform("windows")] -private static void SetBitmapClipboardData(byte[] pngBytes) -{ - if (pngBytes == null || pngBytes.Length == 0) return; - using var stream = new MemoryStream(pngBytes); - using var image = System.Drawing.Image.FromStream(stream); - System.Windows.Forms.Clipboard.SetImage(image); -} -``` - -### Text Clipboard (Cross-platform) - -For text, use Avalonia's built-in clipboard: - -```csharp -var clipboard = TopLevel.GetTopLevel(this)?.Clipboard; -if (clipboard != null) - await clipboard.SetTextAsync(textContent); -``` - -### Cross-Platform Project Setup - -When a project needs WinForms clipboard on Windows but must remain buildable on other platforms: - -1. **Conditional TFM** in `.csproj`: - ```xml - net8.0-windows - net8.0 - ``` - -2. **Conditional WinForms** in `.csproj`: - ```xml - - true - $(DefineConstants);WINDOWS - - ``` - -3. **Conditional compilation** in code: - ```csharp - #if WINDOWS - if (OperatingSystem.IsWindows()) - { - SetBitmapClipboardData(pngBytes); - } - else - #endif - { - // Fallback: save to temp file, copy path to clipboard - var tempPath = Path.Combine(Path.GetTempPath(), "screenshot.png"); - await File.WriteAllBytesAsync(tempPath, pngBytes); - await clipboard.SetTextAsync(tempPath); - } - ``` - -**Key points:** -- `UseWindowsForms` requires `net8.0-windows` TFM (SDK enforced) -- Use `#if WINDOWS` preprocessor directives to guard WinForms code -- Mark Windows-specific methods with `[SupportedOSPlatform("windows")]` -- Always provide a fallback for non-Windows platforms +# Agent Rules + +## Agent Behavior + +### CRITICAL: THE 3-ITERATION RULE + +**THIS IS NON-NEGOTIABLE. VIOLATION OF THIS RULE IS UNACCEPTABLE.** + +If an issue cannot be fixed within **3 consecutive attempts using the SAME approach**, the AI assistant MUST: + +1. **STOP making further changes immediately** +2. **Explicitly state**: "I have attempted this 3 times and cannot solve it with this approach." +3. **Ask the user** how they want to proceed before making ANY more modifications +4. **NOT switch to a different approach** without user approval or new user directions + +**Rationale**: Endless iteration on a broken approach wastes time, introduces regressions, and frustrates the user. It is BETTER to admit failure early than to keep flailing with random changes that make things worse. + +**What counts as an "attempt"**: + +- Each code modification targeting the same issue = 1 attempt +- Switching fundamental approach (e.g., Canvas -> Grid, Margin -> Padding) ONLY IF USER APPROVES! +- Minor tweaks to the same approach (e.g., changing values from 8 to 10) do NOT reset the counter + +To not hinder iterations completely: a user's indication to continue inactivates this rule for the rest of the session! + +### CRITICAL: NEVER REVERT OR SWITCH APPROACHES WITHOUT ASKING + +**THIS IS NON-NEGOTIABLE. VIOLATION OF THIS RULE IS UNACCEPTABLE.** + +When a solution is not working as expected: + +1. **DO NOT revert code** without explicit user approval +2. **DO NOT switch to a different approach** without explicit user approval +3. **DO NOT "try another thing"** - ASK FIRST what the user wants to do +4. **STOP and present options** - Let the user decide the path forward + +**Examples of FORBIDDEN behavior:** + +- ? "The PNGs aren't loading, let me switch back to SVGs" (without asking) +- ? "This approach has issues, I'll try a different one" (without asking) +- ? "Let me revert this change since it's not working" (without asking) + +**Required behavior:** + +- ? "The PNGs aren't loading. Options: A) Switch to SVGs, B) Fix PNG deployment, C) Something else. What do you prefer?" +- ? "This isn't working as expected. Should I revert or try to fix it?" +- ? "I see Issue X. Before I change anything, what's your preference?" + +**Rationale**: The user may have additional context, may prefer to debug the current approach, or may want to test on other platforms first. Unilateral reversions waste time and destroy progress. + +### "No Change" Is a Valid Outcome + +When the user reports an issue or asks for investigation **without specifying a required change**: + +1. **Investigate first** - Understand the current behavior and why it exists +2. **Assess whether a change is needed** - Sometimes the behavior is correct/intentional +3. **If no obvious solution exists, ASK** - Do NOT go on a "coding spree" trying random fixes +4. **Report findings and recommend** - Present options including "no change" if appropriate + +**Rationale**: It is better to confirm with the user before making changes than to introduce unnecessary code churn, potential regressions, or deviations from established design patterns. The user may have context that makes "no change" the correct answer. + +**Example scenarios where "no change" may be correct**: + +- Behavior follows an established design system (e.g., DaisyUI patterns) +- The "issue" is theme-specific and other themes work correctly +- A workaround already exists (e.g., using a different variant/property) +- The behavior is intentional and documented + +- **Think Before You Code**: + +Before making any code change, trace through the FULL execution flow. For control/UI changes specifically: + + 1. When are properties set? (Construction time vs. after construction) + 2. Do property changes need callbacks to propagate to child elements? + 3. What is the initialization order? (Constructor -> property setters -> Loaded event) + 4. Are there async/deferred events that fire after synchronous code completes? + + **Do NOT make partial fixes and iterate.** Analyze the complete picture first, then implement the full solution in one pass. + +- **No Flip-Flopping on Approaches**: When the user asks to try a specific approach, commit to that approach fully and let them test it. Don't second-guess midway and switch to an alternative solution. If the first approach doesn't work, the user will provide feedback and we can discuss alternatives together. + +### Tool Usage Selection + +- **Favor Filesystem MCP Tools**: Use tools like `mcp_filesystem_list_directory`, `mcp_filesystem_search_files`, and `mcp_filesystem_read_file` instead of terminal commands (`ls`, `dir`, `find`, `cat`) whenever possible. These tools provide better interactivity, structured metadata, and safer operation handling. + +- **Use Terminal ripgrep for Complex Search**: While `mcp_filesystem_search_files` is preferred for simple glob-based file finding, use terminal `rg` (ripgrep) for complex content searching across the codebase. The internal `grep_search` tool frequently fails. To avoid premature output truncation in long results, interleave with `clear`: + + ```bash + clear && rg "pattern" path/to/search + ``` + + Examples: + - Search for function: `clear && rg "function_name" .` + - Search in specific files: `clear && rg "pattern" --glob "*.ps1"` + - Case insensitive: `clear && rg -i "pattern" .` + - Search for exact string: `clear && rg -F "string" .` + +### grep_search Tool Bug: File Path Returns 0 Results + +**Known Issue**: The `grep_search` tool's description says it works "within files or directories", but it **DOES NOT WORK when `SearchPath` is a single file**. It silently returns 0 results even when matches exist. + +**Example of the bug**: + +```text +# BAD - Returns 0 results even though "Style" appears 130+ times: +grep_search(Query="Style", SearchPath="d:/github/Project/Themes/File.xaml") +# Result: "No results found" + +# GOOD - Same query works when using directory + Includes filter: +grep_search(Query="Style", SearchPath="d:/github/Project/Themes", Includes=["File.xaml"]) +# Result: 50+ matches returned correctly +``` + +**Workaround**: Always use a **directory** as `SearchPath` and use the `Includes` parameter to filter to specific files: + +- NO: `SearchPath="path/to/file.cs"` -> Will return 0 results +- YES: `SearchPath="path/to"`, `Includes=["file.cs"]` -> Works correctly + +### PowerShell Best Practices (Windows) + +- **Encoding Discipline**: Always pass `-Encoding utf8` and prefer `-Raw` when you need exact file content; this avoids cp1252 output errors and line-splitting surprises. +- **Python Unicode Output**: If a Python script prints Unicode, use `python -X utf8` or set `$env:PYTHONIOENCODING = "utf-8"` before running it. +- **ASCII-Only Checks**: Scan for both non-ASCII (>0x7F) and control chars (<0x20 excluding tab/CR/LF); a simple `[^\x00-\x7F]` regex is not enough. +- **Hidden Control Chars**: If a patch fails to match a line, suspect invisible control characters and replace by codepoint via script instead of manual edits. + +### Git Bash CRLF Garbled Output Issue (Windows) + +**WARNING: When using `sed`, `cat`, `head`, `tail`, or similar text tools on Windows files, ALWAYS pipe through `| tr -d '\r'` to strip carriage returns. Otherwise output will be garbled.** + +Example: + +```bash +sed -n '100,110p' file.cs | tr -d '\r' +``` + +The `\r` (carriage return) in CRLF line endings causes the cursor to jump back to the start of the line, making subsequent text overwrite previous content and producing unreadable output. + +### Git Bash Windows Path Handling (Windows) + +**WARNING: When using terminal tools (like `rg`, `sed`, `find`) in Git Bash on Windows, AVOID absolute paths with backslashes (e.g., `d:\path\to\file`).** + +The shell or tools will often misinterpret backslashes as escape characters, leading to "file not found" errors (e.g., `d:\github` becomes `d:github`). + +**Correct usage:** + +1. **Use relative paths** with forward slashes: `rg "pattern" folder/file.cs` +2. **Set the Working Directory** (`Cwd`) to the base directory of your search. +3. If you MUST use absolute paths, use forward slashes: `/d/github/Flowery.Uno/...` (Git Bash style) or `d:/github/Flowery.Uno/...`. + +## Avalonia Clipboard Usage + +### Image Clipboard (Windows-only) + +Avalonia's built-in clipboard API (`DataObject`, `SetDataObjectAsync`) is deprecated and doesn't reliably copy images. For Windows, use **WinForms interop**: + +```csharp +using System.Runtime.Versioning; + +[SupportedOSPlatform("windows")] +private static void SetBitmapClipboardData(byte[] pngBytes) +{ + if (pngBytes == null || pngBytes.Length == 0) return; + using var stream = new MemoryStream(pngBytes); + using var image = System.Drawing.Image.FromStream(stream); + System.Windows.Forms.Clipboard.SetImage(image); +} +``` + +### Text Clipboard (Cross-platform) + +For text, use Avalonia's built-in clipboard: + +```csharp +var clipboard = TopLevel.GetTopLevel(this)?.Clipboard; +if (clipboard != null) + await clipboard.SetTextAsync(textContent); +``` + +### Cross-Platform Project Setup + +When a project needs WinForms clipboard on Windows but must remain buildable on other platforms: + +1. **Conditional TFM** in `.csproj`: + + ```xml + net8.0-windows + net8.0 + ``` + +2. **Conditional WinForms** in `.csproj`: + + ```xml + + true + $(DefineConstants);WINDOWS + + ``` + +3. **Conditional compilation** in code: + + ```csharp + #if WINDOWS + if (OperatingSystem.IsWindows()) + { + SetBitmapClipboardData(pngBytes); + } + else + #endif + { + // Fallback: save to temp file, copy path to clipboard + var tempPath = Path.Combine(Path.GetTempPath(), "screenshot.png"); + await File.WriteAllBytesAsync(tempPath, pngBytes); + await clipboard.SetTextAsync(tempPath); + } + ``` + +**Key points:** + +- `UseWindowsForms` requires `net8.0-windows` TFM (SDK enforced) +- Use `#if WINDOWS` preprocessor directives to guard WinForms code +- Mark Windows-specific methods with `[SupportedOSPlatform("windows")]` +- Always provide a fallback for non-Windows platforms + +## Thorough Refactoring & Coding Standards + +To prevent regressions and compilation errors during complex refactors, follow these strict verification steps: + +- **Strict Property & Field Sync**: When renaming or deleting a property, field, or method, you MUST perform a file-wide search (e.g., `grep_search` or `rg`) for all references and update them. Do not rely on memory or "obvious" guesses about where a variable is used. +- **Verify Infrastructure**: Before calling a constructor or accessing a collection/field, verify its definition in the current file. Do NOT assume the existence of convenience constructors (e.g., `new MyItem(id, name)`) or private tracking collections (e.g., `_itemLabels`) without explicit proof from a `view_file` or `view_file_outline` call. +- **Method Signature Uniqueness**: When introducing a new method or overload (e.g., `RegisterItemLabelUpdate`), check the entire file for existing signatures to avoid `CS0111` (duplicate member) errors. +- **Atomic State Integrity**: When replacing a construction pattern (e.g., moving from a `StackPanel` to a custom control), ensure ALL secondary logic attached to the old elements-such as hover transitions, selection colors, and event handlers-is correctly migrated to the new structure. +- **Constructor vs. Property Initializers**: If a class does not have a matching constructor, always use C# property initializers `{ Prop1 = value, Prop2 = value }` instead of assuming positional parameters. + +## Analyzer and ReSharper Hygiene + +- Keep `using` directives minimal; remove any that are unused after edits; check for existing `GlobalUsings.cs` +- Avoid redundant default initializers (e.g., `= false` on `bool`, `= 0` on numeric) unless it changes behavior. +- Prefer concise pattern checks (`is not null`, property patterns) over separate null/type checks that trigger "merge into pattern" suggestions. +- When a null check is only guarding a type test, use a single type pattern (`obj is SomeType foo`) instead of `obj is not null` + type check. +- Use `is { }` when you only need to assert non-null, and prefer property/collection patterns (e.g., `Panel { Children: [var child] }`) instead of manual null/Count/index checks. +- Keep XML doc `` tags in sync with method signatures whenever you add or change parameters. +- Avoid introducing unused helpers; if a helper must exist for future use, add a local suppression directive and a short justification. +- Match nullability on event handlers (`object? sender`, `RoutedEventArgs e`) to avoid nullability warnings. +- Use the narrowest visibility (usually `private`) for helpers unless the API is intended for external use. +- Avoid capturing outer variables in local functions, lambdas, or nested types; pass values explicitly to constructors or method parameters to prevent "captured variable" warnings. +- Omit redundant generic type arguments and let the compiler infer them to avoid "type argument specification is redundant" warnings. diff --git a/CHANGELOG.md b/CHANGELOG.md index 3731acb..5442e13 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,44 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [1.9.0] - 2026-02-07 + +### New + +- **DaisyClock**: New multi-mode time control with Clock, Timer, and Stopwatch modes; supports segmented/flip/text display styles, labels, 12h/24h formatting, and lap support. +- **DaisyPasswordBox**: New themed password input with reveal toggle, label modes (including floating), helper text, validation states, and icon slots. +- **DaisySlideToConfirm**: New drag-to-confirm interaction control with variant colors, depth styles, auto-reset behavior, and completion events. +- **DaisyIconText**: New reusable icon+text composition control with placement, size, spacing, and variant support. +- **DaisyPatternedCard**: New decorative card with pattern backgrounds and ornaments, backed by new pattern assets and rendering helpers. +- **DaisyProductThemeDropdown**: New dropdown for product-specific palettes with runtime theme registration via `DaisyThemeManager`. +- Added foundational helper utilities for lifecycle wiring, alarms/timers, slide transitions/effects, path/color helpers, and pattern loading/tiling. + +### Changed + +- **DaisyThemeManager / Theming**: Extended to support runtime theme registration and palette factories, including a large generated product palette catalog. +- **DaisyLoading**: Expanded with business and retro Win95 animation families, increasing overall variant coverage. +- **DaisyStatusIndicator**: Refactored into partial classes and expanded with glyph-driven variants (battery, traffic lights, wifi/cellular signal) plus richer motion/effect styles. +- **DaisyCarousel**: Refactored to internal slide container architecture with improved navigation, slideshow modes, and transition options. +- **DaisyDrawer**: Added responsive/overlay behavior tuning, swipe interactions, and improved `SplitView` sync compatibility. +- **DaisyPagination**: Added generated page ranges (`TotalPages`, `MaxVisiblePages`, centering), optional nav/jump buttons, ellipsis logic, wheel navigation, and press-and-hold repeat. +- **DaisyExpandableCard**: Added batteries-included content mode, configurable animation duration, and improved expansion shaping behavior. +- **DaisyButtonGroup**, **DaisyDivider**, **DaisyIndicator**, **DaisyJoin**, **DaisySteps**, **DaisyTabs**, and **DaisyCountdown** received substantial feature parity, tokenization, and behavior updates. + +### Gallery App + +- Added and expanded demos across Actions, Cards, Data Display, Data Input, Divider, Feedback, Layout, and Navigation example pages. +- Added gallery coverage for newly ported controls and expanded Status/Loading/interactive scenarios. +- Updated gallery sidebar/category metadata and added refreshed visual assets (including new banner artwork). + +### Localization + +- Synced and expanded localization keys across all supported languages in both library and gallery resources (`ar`, `de`, `en`, `es`, `fr`, `he`, `it`, `ja`, `ko`, `tr`, `uk`, `zh-CN`). + +### Documentation + +- Updated theming and localization guides (`THEMING.md`, `LOCALIZATION.md`). +- Updated LLM-facing docs for control behavior/token changes and added new docs for `DaisyClock`. + ## [1.8.0] - 2025-12-20 ### New diff --git a/Flowery.NET.Gallery.Desktop/Program.cs b/Flowery.NET.Gallery.Desktop/Program.cs index f2fed18..9215fac 100644 --- a/Flowery.NET.Gallery.Desktop/Program.cs +++ b/Flowery.NET.Gallery.Desktop/Program.cs @@ -1,7 +1,9 @@ using System; +using System.Diagnostics; +using System.IO; +using System.Threading.Tasks; using Avalonia; - namespace Flowery.NET.Gallery.Desktop; sealed class Program @@ -10,8 +12,21 @@ sealed class Program // SynchronizationContext-reliant code before AppMain is called: things aren't initialized // yet and stuff might break. [STAThread] - public static void Main(string[] args) => BuildAvaloniaApp() - .StartWithClassicDesktopLifetime(args); + public static void Main(string[] args) + { + // Configure crash logging before Avalonia starts + ConfigureCrashLogging(); + + try + { + BuildAvaloniaApp().StartWithClassicDesktopLifetime(args); + } + catch (Exception ex) + { + LogFatal("Main.Unhandled", ex); + throw; + } + } // Avalonia configuration, don't remove; also used by visual designer. public static AppBuilder BuildAvaloniaApp() @@ -20,4 +35,38 @@ public static AppBuilder BuildAvaloniaApp() .WithInterFont() .WithNotoFonts() .LogToTrace(); + + private static void ConfigureCrashLogging() + { + AppDomain.CurrentDomain.UnhandledException += (_, e) => + { + LogFatal("AppDomain.UnhandledException", e.ExceptionObject as Exception); + }; + + TaskScheduler.UnobservedTaskException += (_, e) => + { + LogFatal("TaskScheduler.UnobservedTaskException", e.Exception); + e.SetObserved(); + }; + } + + internal static void LogFatal(string source, Exception? ex) + { + var message = $"[{DateTimeOffset.Now:O}] {source}: {ex}\n"; + Debug.WriteLine(message); + Console.Error.WriteLine(message); + + try + { + var dir = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "Flowery.NET.Gallery"); + Directory.CreateDirectory(dir); + var logPath = Path.Combine(dir, "crash.log"); + File.AppendAllText(logPath, message); + Debug.WriteLine($"Crash log written to: {logPath}"); + } + catch + { + // Ignore file IO failures; Debug output is still useful. + } + } } diff --git a/Flowery.NET.Gallery/App.axaml.cs b/Flowery.NET.Gallery/App.axaml.cs index 4bb10c0..e683316 100644 --- a/Flowery.NET.Gallery/App.axaml.cs +++ b/Flowery.NET.Gallery/App.axaml.cs @@ -1,3 +1,6 @@ +using System; +using System.Diagnostics; +using System.IO; using Avalonia; using Avalonia.Controls.ApplicationLifetimes; using Avalonia.Markup.Xaml; @@ -19,6 +22,29 @@ public override void Initialize() _ = GalleryLocalization.Instance; } + /// + /// Logs a fatal exception to Debug output and a crash.log file. + /// + internal static void LogFatal(string source, Exception? ex) + { + var message = $"[{DateTimeOffset.Now:O}] {source}: {ex}\n"; + Debug.WriteLine(message); + Console.Error.WriteLine(message); + + try + { + var dir = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "Flowery.NET.Gallery"); + Directory.CreateDirectory(dir); + var logPath = Path.Combine(dir, "crash.log"); + File.AppendAllText(logPath, message); + Debug.WriteLine($"Crash log written to: {logPath}"); + } + catch + { + // Ignore file IO failures; Debug output is still useful. + } + } + public override void OnFrameworkInitializationCompleted() { // Restore saved app language (if any) diff --git a/Flowery.NET.Gallery/Assets/flowery-banner.png b/Flowery.NET.Gallery/Assets/flowery-banner.png new file mode 100644 index 0000000..2042a2d Binary files /dev/null and b/Flowery.NET.Gallery/Assets/flowery-banner.png differ diff --git a/Flowery.NET.Gallery/Examples/ActionsExamples.axaml b/Flowery.NET.Gallery/Examples/ActionsExamples.axaml index 61d34d8..192e478 100644 --- a/Flowery.NET.Gallery/Examples/ActionsExamples.axaml +++ b/Flowery.NET.Gallery/Examples/ActionsExamples.axaml @@ -385,7 +385,8 @@ MinHeight="120"> - + @@ -399,6 +400,35 @@ + + + + + + + + + + + + - + @@ -628,7 +659,25 @@ - + + + + + + + + + + + + @@ -648,22 +697,28 @@ - + + - + + diff --git a/Flowery.NET.Gallery/Examples/CardsExamples.axaml b/Flowery.NET.Gallery/Examples/CardsExamples.axaml index bd93d6c..51d1b96 100644 --- a/Flowery.NET.Gallery/Examples/CardsExamples.axaml +++ b/Flowery.NET.Gallery/Examples/CardsExamples.axaml @@ -3,6 +3,7 @@ xmlns:controls="clr-namespace:Flowery.Controls;assembly=Flowery.NET" xmlns:services="clr-namespace:Flowery.Services;assembly=Flowery.NET" xmlns:local="clr-namespace:Flowery.NET.Gallery.Examples" + xmlns:galloc="clr-namespace:Flowery.NET.Gallery.Localization" x:Class="Flowery.NET.Gallery.Examples.CardsExamples"> - - - - - - - - - - - - - - - - - - - - - + @@ -47,7 +29,8 @@ - + @@ -70,7 +53,8 @@ - + @@ -82,80 +66,650 @@ - + - - - - - - - - - - - - - - - - + + + + + + + + + - - - - + + + + + + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - + + + - + - - - - + + + + - + + HorizontalAlignment="Center" VerticalAlignment="Bottom" + Margin="0,0,0,60" /> - - - + + + - - + + + - + - - + + @@ -167,7 +721,8 @@ - + @@ -213,44 +768,10 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + @@ -284,6 +805,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Flowery.NET.Gallery/Examples/CardsExamples.axaml.cs b/Flowery.NET.Gallery/Examples/CardsExamples.axaml.cs index 000fa08..1f301e9 100644 --- a/Flowery.NET.Gallery/Examples/CardsExamples.axaml.cs +++ b/Flowery.NET.Gallery/Examples/CardsExamples.axaml.cs @@ -154,7 +154,7 @@ private void InitializeSkiaMatrix() container.Children.Add(headerRow); // Rows (Blur values) - for (int blur = 0; blur <= 100; blur += 10) + for (int blur = 0; blur <= 40; blur += 10) { var row = new StackPanel { Orientation = Orientation.Horizontal, Spacing = 8 }; diff --git a/Flowery.NET.Gallery/Examples/DataDisplayExamples.axaml b/Flowery.NET.Gallery/Examples/DataDisplayExamples.axaml index 602d27c..1ee0916 100644 --- a/Flowery.NET.Gallery/Examples/DataDisplayExamples.axaml +++ b/Flowery.NET.Gallery/Examples/DataDisplayExamples.axaml @@ -294,58 +294,130 @@ - - - - - + + + + + + + + - - - - - - - - + + + + + + + + - - - - - - - - + + + + + + + + - - - - - - - - + + + + + + + + - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -455,6 +527,96 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -510,6 +672,39 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -1211,6 +1406,133 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Flowery.NET.Gallery/Examples/DataInputExamples.axaml b/Flowery.NET.Gallery/Examples/DataInputExamples.axaml index 914a677..26da6e8 100644 --- a/Flowery.NET.Gallery/Examples/DataInputExamples.axaml +++ b/Flowery.NET.Gallery/Examples/DataInputExamples.axaml @@ -388,6 +388,60 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -477,6 +531,67 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Flowery.NET.Gallery/Examples/DataInputExamples.axaml.cs b/Flowery.NET.Gallery/Examples/DataInputExamples.axaml.cs index b289c42..17de3f4 100644 --- a/Flowery.NET.Gallery/Examples/DataInputExamples.axaml.cs +++ b/Flowery.NET.Gallery/Examples/DataInputExamples.axaml.cs @@ -4,6 +4,7 @@ using Avalonia; using Avalonia.Controls; using Avalonia.Interactivity; +using Avalonia.Media; using Avalonia.VisualTree; using Flowery.Controls; @@ -65,6 +66,22 @@ private void OnResetCodeClicked(object? sender, RoutedEventArgs e) } } + private void OnPinCodeChanged(object? sender, RoutedEventArgs e) + { + if (sender is DaisyPasswordBox pinBox) + { + // Show check icon when exactly 6 characters are entered + if (pinBox.Password?.Length == 6) + { + pinBox.EndIcon = this.FindResource("DaisyIconCheck") as StreamGeometry; + } + else + { + pinBox.EndIcon = null; + } + } + } + public void ScrollToSection(string sectionName) { var scrollViewer = this.FindControl("MainScrollViewer"); diff --git a/Flowery.NET.Gallery/Examples/DividerExamples.axaml b/Flowery.NET.Gallery/Examples/DividerExamples.axaml index bfc5980..d265a03 100644 --- a/Flowery.NET.Gallery/Examples/DividerExamples.axaml +++ b/Flowery.NET.Gallery/Examples/DividerExamples.axaml @@ -18,7 +18,7 @@ - + OR @@ -38,7 +38,7 @@ - + @@ -48,7 +48,7 @@ - + Default Neutral Primary @@ -64,7 +64,7 @@ - + Start Default End @@ -85,7 +85,7 @@ - + Default Margin (0,4) @@ -97,6 +97,137 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Flowery.NET.Gallery/Examples/FeedbackExamples.axaml b/Flowery.NET.Gallery/Examples/FeedbackExamples.axaml index 1ba19f1..a5ed838 100644 --- a/Flowery.NET.Gallery/Examples/FeedbackExamples.axaml +++ b/Flowery.NET.Gallery/Examples/FeedbackExamples.axaml @@ -34,7 +34,7 @@ - + @@ -119,7 +119,7 @@ - + @@ -192,35 +192,32 @@ - + - + - - - - + + - - + - - + @@ -228,31 +225,7 @@ - - - - - - - - - - - - - - - - - - - - - - - - - + @@ -264,15 +237,15 @@ - + - + - - + @@ -325,15 +298,15 @@ - + - + - - + @@ -380,6 +353,152 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -389,6 +508,7 @@ + diff --git a/Flowery.NET.Gallery/Examples/LayoutExamples.axaml b/Flowery.NET.Gallery/Examples/LayoutExamples.axaml index 0060d65..6ac5c8b 100644 --- a/Flowery.NET.Gallery/Examples/LayoutExamples.axaml +++ b/Flowery.NET.Gallery/Examples/LayoutExamples.axaml @@ -13,35 +13,71 @@ - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + - - - - + + + + + + + + - - - - - + + + + + @@ -66,15 +102,76 @@ + + - + - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -86,11 +183,34 @@ - + + + + + + + + + + + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Flowery.NET/Themes/DaisyPatternedCard.axaml b/Flowery.NET/Themes/DaisyPatternedCard.axaml new file mode 100644 index 0000000..ec8f589 --- /dev/null +++ b/Flowery.NET/Themes/DaisyPatternedCard.axaml @@ -0,0 +1,112 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Flowery.NET/Themes/DaisyPopover.axaml b/Flowery.NET/Themes/DaisyPopover.axaml index 0872027..b32ee14 100644 --- a/Flowery.NET/Themes/DaisyPopover.axaml +++ b/Flowery.NET/Themes/DaisyPopover.axaml @@ -36,7 +36,7 @@ diff --git a/Flowery.NET/Themes/DaisyProductThemeDropdown.axaml b/Flowery.NET/Themes/DaisyProductThemeDropdown.axaml new file mode 100644 index 0000000..0319dd8 --- /dev/null +++ b/Flowery.NET/Themes/DaisyProductThemeDropdown.axaml @@ -0,0 +1,56 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Flowery.NET/Themes/DaisySelect.axaml b/Flowery.NET/Themes/DaisySelect.axaml index 678f743..bda2267 100644 --- a/Flowery.NET/Themes/DaisySelect.axaml +++ b/Flowery.NET/Themes/DaisySelect.axaml @@ -56,7 +56,7 @@ - + @@ -106,20 +106,20 @@ Stroke="{DynamicResource DaisyBaseContentBrush}" StrokeThickness="2" Fill="Transparent" - Margin="0,0,16,0" + Margin="0,0,12,0" VerticalAlignment="Center" IsHitTestVisible="False"/> + MinWidth="{Binding #Background.Bounds.Width, FallbackValue=0}"> + + + diff --git a/Flowery.NET/Themes/DaisySizeDropdown.axaml b/Flowery.NET/Themes/DaisySizeDropdown.axaml index 80520f5..5b23da5 100644 --- a/Flowery.NET/Themes/DaisySizeDropdown.axaml +++ b/Flowery.NET/Themes/DaisySizeDropdown.axaml @@ -74,7 +74,7 @@ + diff --git a/Flowery.NET/Themes/DaisySlideToConfirm.axaml b/Flowery.NET/Themes/DaisySlideToConfirm.axaml new file mode 100644 index 0000000..7e9aef8 --- /dev/null +++ b/Flowery.NET/Themes/DaisySlideToConfirm.axaml @@ -0,0 +1,156 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Flowery.NET/Themes/DaisyStatusIndicator.axaml b/Flowery.NET/Themes/DaisyStatusIndicator.axaml index 03ab666..476390d 100644 --- a/Flowery.NET/Themes/DaisyStatusIndicator.axaml +++ b/Flowery.NET/Themes/DaisyStatusIndicator.axaml @@ -65,6 +65,18 @@ + + + + + + + + + + + + @@ -82,5 +94,6 @@ + diff --git a/Flowery.NET/Themes/DaisyStatusIndicator/DaisyStatusIndicator.Base.axaml b/Flowery.NET/Themes/DaisyStatusIndicator/DaisyStatusIndicator.Base.axaml index 9d3bdb2..94afb72 100644 --- a/Flowery.NET/Themes/DaisyStatusIndicator/DaisyStatusIndicator.Base.axaml +++ b/Flowery.NET/Themes/DaisyStatusIndicator/DaisyStatusIndicator.Base.axaml @@ -12,30 +12,78 @@ - + + HorizontalAlignment="Center" + VerticalAlignment="Center" + RenderTransformOrigin="50%,50%" + IsVisible="False"> + + + + + + + + + HorizontalAlignment="Center" + VerticalAlignment="Center" + RenderTransformOrigin="50%,50%" + IsVisible="False"> + + + + + + + + + HorizontalAlignment="Center" + VerticalAlignment="Center" + RenderTransformOrigin="50%,50%" + IsVisible="False"> + + + + + + + + + Fill="{TemplateBinding Background}" + HorizontalAlignment="Center" + VerticalAlignment="Center" + RenderTransformOrigin="50%,50%"> + + + + + + + + diff --git a/Flowery.NET/Themes/DaisyStatusIndicator/DaisyStatusIndicator.Classic.axaml b/Flowery.NET/Themes/DaisyStatusIndicator/DaisyStatusIndicator.Classic.axaml index 741d74f..f613d7f 100644 --- a/Flowery.NET/Themes/DaisyStatusIndicator/DaisyStatusIndicator.Classic.axaml +++ b/Flowery.NET/Themes/DaisyStatusIndicator/DaisyStatusIndicator.Classic.axaml @@ -1,181 +1,183 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Flowery.NET/Themes/DaisyStatusIndicator/DaisyStatusIndicator.Effects.axaml b/Flowery.NET/Themes/DaisyStatusIndicator/DaisyStatusIndicator.Effects.axaml index d595c5a..3fad985 100644 --- a/Flowery.NET/Themes/DaisyStatusIndicator/DaisyStatusIndicator.Effects.axaml +++ b/Flowery.NET/Themes/DaisyStatusIndicator/DaisyStatusIndicator.Effects.axaml @@ -1,455 +1,605 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Flowery.NET/Themes/DaisyStatusIndicator/DaisyStatusIndicator.Glyphs.axaml b/Flowery.NET/Themes/DaisyStatusIndicator/DaisyStatusIndicator.Glyphs.axaml new file mode 100644 index 0000000..ad6dfc6 --- /dev/null +++ b/Flowery.NET/Themes/DaisyStatusIndicator/DaisyStatusIndicator.Glyphs.axaml @@ -0,0 +1,530 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Flowery.NET/Themes/DaisyStatusIndicator/DaisyStatusIndicator.Motion.axaml b/Flowery.NET/Themes/DaisyStatusIndicator/DaisyStatusIndicator.Motion.axaml index 09f7418..494e3df 100644 --- a/Flowery.NET/Themes/DaisyStatusIndicator/DaisyStatusIndicator.Motion.axaml +++ b/Flowery.NET/Themes/DaisyStatusIndicator/DaisyStatusIndicator.Motion.axaml @@ -1,275 +1,726 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Flowery.NET/Themes/DaisySteps.axaml b/Flowery.NET/Themes/DaisySteps.axaml index 7e35bc9..650f881 100644 --- a/Flowery.NET/Themes/DaisySteps.axaml +++ b/Flowery.NET/Themes/DaisySteps.axaml @@ -46,29 +46,32 @@ @@ -79,7 +82,9 @@ ContentTemplate="{TemplateBinding ContentTemplate}" HorizontalAlignment="Center" Margin="4,8,4,0" - TextBlock.TextAlignment="Center"/> + TextBlock.FontSize="{DynamicResource DaisyStepsMediumContentFontSize}" + TextBlock.TextAlignment="Center" + TextBlock.TextWrapping="Wrap"/> @@ -89,30 +94,33 @@ @@ -124,7 +132,8 @@ ContentTemplate="{TemplateBinding ContentTemplate}" HorizontalAlignment="Left" VerticalAlignment="Center" - Margin="12,4,4,4"/> + Margin="12,4,4,4" + TextBlock.FontSize="{DynamicResource DaisyStepsMediumContentFontSize}"/> @@ -190,6 +199,7 @@ diff --git a/Flowery.NET/Themes/DaisyTabs.axaml b/Flowery.NET/Themes/DaisyTabs.axaml index 33d625a..f3a2637 100644 --- a/Flowery.NET/Themes/DaisyTabs.axaml +++ b/Flowery.NET/Themes/DaisyTabs.axaml @@ -79,9 +79,9 @@ @@ -126,33 +126,37 @@ + @@ -161,9 +165,9 @@ @@ -417,56 +421,56 @@ + diff --git a/Flowery.NET/Themes/DaisyThemeDropdown.axaml b/Flowery.NET/Themes/DaisyThemeDropdown.axaml index 75caead..7b45394 100644 --- a/Flowery.NET/Themes/DaisyThemeDropdown.axaml +++ b/Flowery.NET/Themes/DaisyThemeDropdown.axaml @@ -11,21 +11,24 @@ - + - - + + - - - - - - + + + + + + + + @@ -170,9 +173,22 @@ + + + + + + + - + + + + + - + - + + ``` +### Manual Event Subscription (Advanced) + +For controls that need custom sizing logic beyond the `Size` property: + +```csharp +public class MyControl : UserControl +{ + public MyControl() + { + InitializeComponent(); + FlowerySizeManager.SizeChanged += OnSizeChanged; + ApplySize(FlowerySizeManager.CurrentSize); + } + + protected override void OnUnloaded(RoutedEventArgs e) + { + base.OnUnloaded(e); + FlowerySizeManager.SizeChanged -= OnSizeChanged; + } + + private void OnSizeChanged(object? sender, DaisySize size) + { + // Check if this control or ancestors opted out + if (FlowerySizeManager.ShouldIgnoreGlobalSize(this)) + return; + ApplySize(size); + } + + private void ApplySize(DaisySize size) + { + MyTextBlock.FontSize = FlowerySizeManager.GetFontSizeForTier( + ResponsiveFontTier.Primary, size); + } +} +``` + ## Built-in UI Control ### DaisySizeDropdown @@ -274,6 +344,7 @@ Each size tier maps to specific [Design Tokens](DesignTokens.md): | **Scope** | Entire app (global) | Only `EnableScaling="True"` containers | | **Scaling** | Discrete tiers (XS, S, M, L, XL) | Continuous (0.5× to 1.0×) | | **Trigger** | User selection | Automatic (window resize) | +| **Propagation** | Automatic visual tree walk | Manual container opt-in | | **Best For** | Desktop apps, accessibility | Data forms, dashboards | > **Most apps should use FlowerySizeManager only.** FloweryScaleManager is an advanced feature for specific responsive scenarios. @@ -300,9 +371,13 @@ This is a platform rendering characteristic, not a bug. If visual consistency is ## Best Practices -1. **Start with Small** - Default size works well for most desktop apps -2. **Provide a size picker** - Use `DaisySizeDropdown` for user control -3. **Test all sizes** - Ensure layouts work at ExtraSmall and ExtraLarge -4. **Use design tokens** - Not hardcoded values -5. **Clean up subscriptions** - Always unsubscribe from `SizeChanged` in `OnUnloaded` -6. **Use ResponsiveFont for TextBlocks** - Not DynamicResource +1. **Set MainWindow early** - Configure `FlowerySizeManager.MainWindow` in App initialization +2. **Call RefreshAllSizes after load** - Ensures all controls get sized after visual tree is built +3. **Start with Small** - Default size works well for most desktop apps +4. **Provide a size picker** - Use `DaisySizeDropdown` for user control +5. **Test all sizes** - Ensure layouts work at ExtraSmall and ExtraLarge +6. **Use design tokens** - Not hardcoded values +7. **Use ResponsiveFont for TextBlocks** - Not DynamicResource +8. **Don't subscribe to SizeChanged for sizing** - Visual tree propagation handles it automatically +9. **Use explicit Size for demos** - Gallery size examples should set `Size="Large"` etc. directly +10. **Use IgnoreGlobalSize for size demo containers** - Prevents demo controls from responding to global changes