From c5906c2dc1cf7ffc33cc4f89ad2d2b0806e8d7a1 Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Wed, 19 Nov 2025 16:23:35 -0500 Subject: [PATCH] =?UTF-8?q?#4329=E2=80=94Major=20Terminal.Gui=20v2=20Archi?= =?UTF-8?q?tecture=20Modernization:=20Application=20Decoupling,=20Terminol?= =?UTF-8?q?ogy=20Improvements,=20and=20Nullable=20Migration=20(#4338)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Initial plan * Add comprehensive terminology proposal for Application.Top/Toplevel renaming Co-authored-by: tig <585482+tig@users.noreply.github.com> * Add terminology proposal summary document Co-authored-by: tig <585482+tig@users.noreply.github.com> * Add before/after comparison document for terminology proposal Co-authored-by: tig <585482+tig@users.noreply.github.com> * Add index document for terminology proposal Co-authored-by: tig <585482+tig@users.noreply.github.com> * Add Mermaid diagrams visualizing the terminology proposal Co-authored-by: tig <585482+tig@users.noreply.github.com> * Move terminology docs to docfx/docs per CONTRIBUTING.md guidelines Co-authored-by: tig <585482+tig@users.noreply.github.com> * Update terminology proposal to reflect recent modernization (post-MainLoop removal) Co-authored-by: tig <585482+tig@users.noreply.github.com> * Update terminology proposal - validate against recent driver refactoring changes Co-authored-by: tig <585482+tig@users.noreply.github.com> * Update proposal after massive v2_develop modernization - TopLevels now public, RunState→SessionToken Co-authored-by: tig <585482+tig@users.noreply.github.com> * Update proposal: RunStack → SessionStack to align with SessionToken terminology Co-authored-by: tig <585482+tig@users.noreply.github.com> * Phase 1: Core API rename - Application.Top→Current, TopLevels→SessionStack Co-authored-by: tig <585482+tig@users.noreply.github.com> * Phase 2: Fix test compilation errors for renamed properties Co-authored-by: tig <585482+tig@users.noreply.github.com> * Phase 3: Update documentation files with new terminology Co-authored-by: tig <585482+tig@users.noreply.github.com> * Refactor generic type names and remove unused field Renamed generic type parameters in `Dim` and `Pos` classes for clarity: - `T` was renamed to `TDim` in `Dim.Has` method. - `T` was renamed to `TPos` in `Pos.Has` method. Updated type casting and pattern matching logic to reflect these changes. Removed the unused `_stopAfterFirstIteration` field from the `ApplicationImpl` class to clean up the codebase. * Increase minimum code coverage target to 75% Updated the `codecov.yml` configuration file to raise the `project.default.target` value from 70% to 75%, enforcing stricter code coverage requirements for the overall project. * Add comprehensive unit tests for ApplicationImpl Begin/End logic Added ApplicationImplBeginEndTests with 16 tests covering: - Begin/End argument validation - SessionStack push/pop operations - Current property management - Balanced Begin/End enforcement - Multiple nested Begin/End scenarios - ResetState cleanup behavior - Toplevel activation/deactivation - SessionToken management Tests validate the fragile state management logic in ApplicationImpl.Lifecycle.cs and ApplicationImpl.Run.cs to catch regressions in Current/SessionStack handling. Co-authored-by: tig <585482+tig@users.noreply.github.com> * Refactor ApplicationImplBeginEndTests to work with ApplicationImpl instances directly Tests now: - Create ApplicationImpl instances directly instead of using global Application - Use try-finally blocks to ensure Shutdown() is always called - Work with app.Init() and app.Shutdown() properly - 13 of 16 tests passing (3 tests need adjustment for ResetState behavior) Tests validate fragile Begin/End state management logic without polluting global Application state. Co-authored-by: tig <585482+tig@users.noreply.github.com> * Fix ApplicationImplBeginEndTests: Remove explicit ResetState calls, rely on Shutdown Per maintainer feedback: - Tests should not call ResetState explicitly if they call Shutdown (Shutdown calls ResetState) - Removed all explicit app.ResetState() calls from tests - Tests now only call Shutdown in finally blocks for cleanup - Ensured toplevels are disposed BEFORE Shutdown to satisfy DEBUG_IDISPOSABLE assertions - Fixed test cleanup order to prevent ResetState assertion failures - All 16 tests now pass successfully ResetState is idempotent - the issue was tests calling it unnecessarily and with incorrect disposal ordering. Co-authored-by: tig <585482+tig@users.noreply.github.com> * Phase 1: Enable nullable by default, add directives to all files - Added enable to Terminal.Gui.csproj - Removed #nullable enable from 37 files (now redundant with project setting) - Added #nullable disable to 170 files that don't have nullable enabled yet - Build successful with 279 nullable warnings (expected) Breakdown of files with #nullable disable: - 27 in Views/ directory (will remain disabled per plan) - 143 outside Views/ (need to be fixed in subsequent commits) Next: Systematically fix nullable issues in non-View files. Co-authored-by: tig <585482+tig@users.noreply.github.com> * Phase 2: Remove nullable disable from all non-View files - COMPLETE! - Removed #nullable disable from 142 non-View files - All non-View code is now nullable-enabled - Build successful with 0 errors, only 1 unrelated warning - Created NULLABLE_VIEWS_REMAINING.md documenting the 121 View files still with nullable disable Nullable cleanup summary: ✅ App/: 25 files - nullable enabled ✅ Configuration/: 24 files - nullable enabled ✅ ViewBase/: 30 files - nullable enabled ✅ Drivers/: 25 files - nullable enabled ✅ Drawing/: 18 files - nullable enabled ✅ FileServices/: 7 files - nullable enabled ✅ Input/: 6 files - nullable enabled ✅ Text/: 5 files - nullable enabled ✅ Resources/: 3 files - nullable enabled ⏸️ Views/: 121 files - remain disabled (as requested) Total: 143 files nullable-enabled, 121 View files remain disabled. Co-authored-by: tig <585482+tig@users.noreply.github.com> * WIP: fixing nullability issues. * Fixed final nullability issues. * Moved Arrangment tests * Refactor and improve modularity across multiple classes Refactored `FillRectangles` in `RegionScenario` and `Region` to accept an `IDriver` parameter, reducing reliance on global state. Updated `ApplicationImpl` to replace static method calls with instance methods for better encapsulation. Renamed `Toplevel` to `Current` in `IPopover` and related classes for clarity. Simplified `LineCanvas` methods by removing unnecessary `IDriver` parameters. Added `Id` and `App` properties to `View` for better state management and unique identification. Streamlined the `Driver` property in `View` with a concise getter. Improved formatting and consistency across files, including `Region` and `IntersectionRuneResolver`. Enhanced thread safety in `Region` and cleaned up redundant code. Updated tests to align with interface changes and ensure compatibility. * Refactor to make IDriver dependency explicit Updated `AnsiEscapeSequenceRequest.Send` to accept an `IDriver?` parameter, replacing reliance on `Application.Driver`. Refactored `AnsiRequestScheduler` methods (`SendOrSchedule`, `RunSchedule`, and private `Send`) to propagate the `IDriver?` parameter, ensuring explicit driver dependency. Modified `DriverImpl.QueueAnsiRequest` to pass `this` to `SendOrSchedule`. Updated `AnsiRequestSchedulerTests` to reflect new method signatures, passing `null` for the driver parameter where applicable. Added `` documentation for new parameters to improve clarity. These changes enhance flexibility, maintainability, and testability by reducing reliance on global state and allowing driver substitution in tests. * WIP: Started migrating to View.App Refactored `ApplicationImpl` to ensure proper handling of the `App` property for `Toplevel` instances, improving modularity. Replaced direct references to `Application` with `App` in `Border`, `ShadowView`, and other classes to enhance flexibility and maintainability. Introduced `GetApp` in `View` to allow overrides for retrieving the `App` instance. Updated `Adornment` to use this method. Moved mouse event subscriptions in `Border` to `BeginInit` for proper lifecycle management. Updated unit tests in `ArrangementTests` to use `App.Mouse` instead of `Application.Mouse`, ensuring alignment with the refactored design. Added `BeginInit` and `EndInit` calls for proper initialization during tests. Removed redundant code and improved test assertions. * WIP: Next set of View.App changes Updated `SetClipToScreen`, `SetClip`, and `GetClip` methods to accept an `IDriver` parameter, replacing reliance on the global `Application.Driver`. This improves modularity, testability, and reduces implicit global state usage. - Updated `Driver` property in `View` to use `App?.Driver` as fallback. - Refactored `DimAuto` to use `App?.Screen.Size` with a default for unit tests. - Updated all test cases to align with the new method signatures. - Performed general cleanup for consistency and readability. * Adds View clip tests. * Merged * Merged * wip * Fixed test bug. * Refactored Thickness.Draw to require driver. * Made TextFormatter.Draw require driver. * Code cleanup. * Un did stoopid idea. * Decouped Application.Navigation * MASSIVE - Almost completely decoupled Application from View etc... * Obsolete * Missed some * More cleanup and decoupling. Refactor `ToString` and remove legacy code Refactored `ToString` implementations across `Application`, `DriverImpl`, and `IDriver` to improve consistency and maintainability. Removed the legacy `ToString(IDriver? driver)` method and its associated references. Simplified `ToString` in `DriverImpl` to generate a string representation of the `Contents` buffer. Replaced redundant XML documentation with `` tags to reduce duplication. Cleaned up unused `global using` directives and removed deprecated methods and properties, including `Screen`, `SetCursorVisibility`, and `IsRuneSupported`. Updated test cases in `GuiTestContext` and `DriverAssert` to use the new `ToString` implementation. Improved error messages for better debugging output. Streamlined LINQ queries and removed redundant checks for better readability and performance. Enhanced maintainability by decluttering the codebase, aligning namespaces, and consolidating related changes. * Changes before error encountered Co-authored-by: tig <585482+tig@users.noreply.github.com> * Update docfx/docs to document View.App architecture and instance-based patterns Updated 16 documentation files to reflect the major architectural changes: NEW FILES: - application.md: Comprehensive deep dive on decoupled Application architecture UPDATED FILES: - View.md: Documents View.App property, GetApp(), and instance-based patterns - navigation.md: Shows View.App usage instead of static Application - drivers.md: Documents View.Driver and GetDriver() patterns - keyboard.md: Event handling through View.App - mouse.md: Mouse event handling via View.App - arrangement.md: Updated code examples to use View.App - drawing.md: Rendering examples with instance-based API - cursor.md: Cursor management through View.App - multitasking.md: SessionStack and session management via View.App - Popovers.md: Popover patterns with View.App - cancellable-work-pattern.md: Updated examples - command.md: Command pattern with View.App context - config.md: Configuration access through View.App - migratingfromv1.md: Migration guide for static→instance patterns - newinv2.md: Documents new instance-based architecture All code examples now demonstrate the instance-based API (view.App.Current) instead of obsolete static Application references. Documentation accurately reflects the massive architectural decoupling achieved in this PR. Co-authored-by: tig <585482+tig@users.noreply.github.com> * Add `ToAnsi` support for ANSI escape sequence generation Introduced `ToAnsi` in `IDriver` and `IOutput` interfaces to generate ANSI escape sequences representing the terminal's current state. This enables serialization of terminal content for debugging, testing, and exporting. Implemented `ToAnsi` in `DriverImpl` and `FakeOutput`, supporting both 16-color and RGB modes. Refactored `OutputBase` with helper methods `BuildAnsiForRegion` and `AppendCellAnsi` for efficient ANSI generation. Enhanced `GuiTestContext` with `AnsiScreenShot` for capturing terminal state during tests. Added `ToAnsiTests` for comprehensive validation, including edge cases, performance, and wide/Unicode character handling. Updated documentation to reflect `ToAnsi` functionality and modernized driver architecture. Improved testability, modularity, and performance while removing legacy driver references. * Improve null safety and cleanup in GuiTestContext Enhanced null safety across `GuiTestContext` and `GuiTestContextTests`: - Replaced `a` with `app` for better readability in tests. - Added null checks (`!`, `?.`) to prevent potential null reference exceptions. - Removed redundant `WaitIteration` and duplicate `ScreenShot` calls. Improved error handling and robustness: - Updated shutdown logic to use null-safe calls for `RequestStop` and `Shutdown`. - Applied null-safe invocation for `_applicationImpl.Invoke`. General cleanup: - Removed redundant method calls and improved naming consistency. - Ensured better maintainability and adherence to best practices. * Refactor docs: remove deprecated files, update architecture Removed outdated documentation files related to the terminology proposal (`terminology-before-after.md`, `terminology-diagrams.md`, `terminology-index.md`, `terminology-proposal-summary.md`, `terminology-proposal.md`) from the `Docs` project. These files were either deprecated or consolidated into other documentation. Updated `application.md`: - Added a "View Hierarchy and Run Stack" section with a Mermaid diagram to illustrate the relationship between the view hierarchy and the application session stack. - Added a "Usage Example Flow" section with a sequence diagram to demonstrate the flow of running and stopping views. These changes improve clarity, streamline documentation, and align with the finalized terminology updates for the `Application.Current` and `Application.SessionStack` APIs. * Refactor Init/Run methods to simplify driver handling The `Init` method in `Application` and `IApplication` now accepts only an optional `driverName` parameter, removing the `IDriver` parameter. This simplifies initialization by relying on driver names to determine the appropriate driver. The `Run` methods have been updated to use `driverName` instead of `driver`, ensuring consistency with the updated `Init` method. Replaced redundant inline documentation with `` tags to improve maintainability and consistency. Legacy `Application` methods (`Init`, `Shutdown`, `Run`) have been marked as `[Obsolete]` to signal their eventual deprecation. Test cases have been refactored to align with the updated `Init` method signature, removing unused `driver` parameters. Documentation files have also been updated to reflect these API changes. These changes improve clarity, reduce complexity, and ensure a more consistent API design. * Refactor: Introduce Application.Create() factory method Introduced a new static method `Application.Create()` to create instances of `IApplication`, replacing direct instantiation of `ApplicationImpl`. This enforces a cleaner, recommended pattern for creating application instances. Made the `ApplicationImpl` constructor `internal` to ensure `Application.Create()` is used for instance creation. Refactored test cases across multiple files to use `Application.Create()` instead of directly instantiating `ApplicationImpl`. Simplified object initialization in tests using target-typed `new()` expressions. Updated documentation and examples in `application.md` to reflect the new instance-based architecture and highlight its benefits, such as supporting multiple applications with different drivers. Improved code readability, formatting, and consistency in tests and documentation. Aligned `ApplicationImplBeginEndTests` to use `IApplication` directly, adhering to the new architecture. * Added `Application.StopAll` and fixed coupling issues. Refactored `ApplicationImpl` to use an instance-based approach, replacing the static singleton pattern and Lazy. Introduced `SetInstance` for configuring the singleton instance and updated tests to use `ApplicationImpl.Instance` or explicitly set the `Driver` property. Enabled nullable reference types across the codebase, updating fields and variables to nullable types where applicable. Added null checks to improve safety and prevent runtime errors. Refactored timeout management by introducing tokens for `Application.AddTimeout` and adding a `StopAll` method to `TimedEvents` for cleanup. Updated tests to use `System.Threading.Timer` for independent watchdog timers. Removed legacy code, improved logging for error cases, and updated view initialization to explicitly set `App` or `Driver` in tests. Enhanced test coverage and restructured `ScrollSliderTests` for better readability. Performed general code cleanup, including formatting changes, removal of unused imports, and improved naming consistency. * Refactor: Transition to IApplication interface Refactored the codebase to replace the static `Application` class with the `IApplication` interface, improving modularity, testability, and maintainability. Updated methods like `Application.Run`, `RequestStop`, and `Init` to use the new interface. Marked static members `SessionStack` and `Current` as `[Obsolete]` and delegated their functionality to `ApplicationImpl.Instance`. Updated XML documentation to reflect these changes. Simplified code by removing redundant comments, unused code, and converting methods like `GetMarginThickness` to single-line expressions. Improved null safety with null-conditional operators in `ToplevelTransitionManager`. Enhanced consistency with formatting updates, logging improvements, and better error handling. Updated `Shortcut` and other classes to align with the new interface-based design. Made breaking changes, including the removal of the `helpText` parameter in the `Shortcut` constructor. Updated `Wizard`, `Dialog`, and `GraphView` to use `IApplication` methods. Adjusted `ViewportSettings` and `HighlightStates` for better behavior. * Enhance null-safety and simplify codebase Improved null-safety by adopting nullable reference types and adding null-forgiving operators (`!`) where appropriate. Replaced direct method calls with null-safe calls using the null-conditional operator (`?.`) to prevent potential `NullReferenceException`. Removed default parameter values in test methods to enforce explicit parameter passing. Refactored test classes to remove unnecessary dependencies on `ITestOutputHelper`. Fixed a bug in `WindowsOutput.cs` by setting `_force16Colors` to `false` to avoid reliance on a problematic driver property. Updated `SessionTokenTests` to use null-forgiving operators for clarity in intentional null usage. Simplified graph and UI updates by ensuring safe access to properties and methods. Cleaned up namespaces and removed unused `using` directives for better readability. Updated `Dispose` methods to use null-safe calls and replaced nullable driver initialization with non-nullable initialization in `ScrollSliderTests` to ensure proper instantiation. * Refactor test code to use nullable `App` property Replaced direct `Application` references with `App` property across test classes to improve encapsulation and robustness. Updated `GuiTestContext` to use a nullable `App` property, replacing `_applicationImpl` for consistency. Refactored key event handling to use `App.Driver` and revised `InitializeApplication` and `CleanupApplication` methods to ensure safe usage of the nullable `App` property. Updated `Then` callbacks to explicitly pass `App` for clarity. Replaced `Application.QuitKey` with `context.App?.Keyboard.RaiseKeyDownEvent` to ensure context-specific event handling. Refactored `EnableForDesign` logic in `MenuBarv2Tests` and `PopoverMenuTests` to operate on the correct application instance. Improved null safety in test assertions and revised `RequestStop` and `Shutdown` calls to use `App?.RequestStop` and `App?.Shutdown`. Updated navigation logic to use `Terminal.Gui.App.Application` for namespace consistency. Enhanced exception handling in the `Invoke` method and performed general cleanup to align with modern C# practices, improving maintainability and readability. * Commented out exception handling in Application.Shutdown The `try-catch` block around `Application.Shutdown` was commented out, disabling the logging of exceptions thrown after a test exited. This change removes the `catch` block that used `Debug.WriteLine` for logging. The `finally` block remains intact, ensuring cleanup operations such as clearing `View.Instances` and resetting the application state are still executed. * Fixes #4394 - Changing Theme at Runtime does not Update Some Properties * Tweaks to config format. --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: Tig Co-authored-by: tig <585482+tig@users.noreply.github.com> --- Examples/CommunityToolkitExample/Program.cs | 2 +- Examples/ReactiveExample/Program.cs | 2 +- Examples/ReactiveExample/TerminalScheduler.cs | 2 +- Examples/UICatalog/Resources/config.json | 5 +- Examples/UICatalog/Scenario.cs | 4 +- .../UICatalog/Scenarios/AllViewsTester.cs | 2 +- .../AnimationScenario/AnimationScenario.cs | 2 +- .../Scenarios/AnsiRequestsScenario.cs | 115 +-- Examples/UICatalog/Scenarios/Bars.cs | 16 +- .../UICatalog/Scenarios/CombiningMarks.cs | 6 +- .../Scenarios/ConfigurationEditor.cs | 2 +- Examples/UICatalog/Scenarios/ContextMenus.cs | 120 +-- Examples/UICatalog/Scenarios/CsvEditor.cs | 2 +- Examples/UICatalog/Scenarios/Images.cs | 2 +- Examples/UICatalog/Scenarios/Mazing.cs | 6 +- Examples/UICatalog/Scenarios/Menus.cs | 2 +- Examples/UICatalog/Scenarios/Navigation.cs | 2 +- Examples/UICatalog/Scenarios/Notepad.cs | 2 +- Examples/UICatalog/Scenarios/Progress.cs | 2 +- .../UICatalog/Scenarios/RegionScenario.cs | 38 +- Examples/UICatalog/Scenarios/Shortcuts.cs | 54 +- .../Scenarios/SingleBackgroundWorker.cs | 4 +- Examples/UICatalog/Scenarios/TableEditor.cs | 2 +- Examples/UICatalog/Scenarios/Themes.cs | 8 +- Examples/UICatalog/Scenarios/TreeUseCases.cs | 12 +- .../UICatalog/Scenarios/TreeViewFileSystem.cs | 2 +- .../UICatalog/Scenarios/ViewExperiments.cs | 10 +- .../Scenarios/WindowsAndFrameViews.cs | 2 +- Examples/UICatalog/UICatalog.cs | 21 +- Examples/UICatalog/UICatalog.csproj | 1 + Examples/UICatalog/UICatalogTop.cs | 3 +- NULLABLE_VIEWS_REMAINING.md | 163 ++++ PR_DESCRIPTION_UPDATED.md | 322 ++++++++ Terminal.Gui/App/Application.Current.cs | 18 + Terminal.Gui/App/Application.Driver.cs | 8 +- Terminal.Gui/App/Application.Keyboard.cs | 17 +- Terminal.Gui/App/Application.Lifecycle.cs | 66 +- Terminal.Gui/App/Application.Mouse.cs | 18 +- Terminal.Gui/App/Application.Navigation.cs | 5 +- Terminal.Gui/App/Application.Popover.cs | 4 +- Terminal.Gui/App/Application.Run.cs | 33 +- Terminal.Gui/App/Application.Screen.cs | 4 +- Terminal.Gui/App/Application.Toplevel.cs | 18 - Terminal.Gui/App/Application.cs | 89 +-- Terminal.Gui/App/ApplicationImpl.Driver.cs | 3 +- Terminal.Gui/App/ApplicationImpl.Lifecycle.cs | 36 +- Terminal.Gui/App/ApplicationImpl.Run.cs | 133 +-- Terminal.Gui/App/ApplicationImpl.Screen.cs | 23 +- Terminal.Gui/App/ApplicationImpl.cs | 87 +- Terminal.Gui/App/ApplicationNavigation.cs | 10 +- Terminal.Gui/App/ApplicationPopover.cs | 45 +- Terminal.Gui/App/CWP/CWPEventHelper.cs | 3 +- Terminal.Gui/App/CWP/CWPPropertyHelper.cs | 2 - Terminal.Gui/App/CWP/CWPWorkflowHelper.cs | 3 +- Terminal.Gui/App/CWP/CancelEventArgs.cs | 1 - Terminal.Gui/App/CWP/EventArgs.cs | 1 - Terminal.Gui/App/CWP/ResultEventArgs.cs | 3 +- Terminal.Gui/App/CWP/ValueChangedEventArgs.cs | 1 - .../App/CWP/ValueChangingEventArgs.cs | 3 +- Terminal.Gui/App/Clipboard/Clipboard.cs | 5 +- Terminal.Gui/App/Clipboard/ClipboardBase.cs | 1 + .../App/Clipboard/ClipboardProcessRunner.cs | 2 - Terminal.Gui/App/IApplication.cs | 60 +- Terminal.Gui/App/IPopover.cs | 11 +- Terminal.Gui/App/Keyboard/IKeyboard.cs | 5 +- Terminal.Gui/App/Keyboard/KeyboardImpl.cs | 39 +- .../App/MainLoop/ApplicationMainLoop.cs | 35 +- .../App/MainLoop/IApplicationMainLoop.cs | 10 +- .../App/MainLoop/IMainLoopCoordinator.cs | 3 +- .../App/MainLoop/MainLoopCoordinator.cs | 34 +- .../App/MainLoop/MainLoopSyncContext.cs | 73 +- Terminal.Gui/App/Mouse/IMouse.cs | 10 +- Terminal.Gui/App/Mouse/IMouseGrabHandler.cs | 1 - Terminal.Gui/App/Mouse/MouseGrabHandler.cs | 1 - Terminal.Gui/App/Mouse/MouseImpl.cs | 26 +- Terminal.Gui/App/PopoverBaseImpl.cs | 22 +- Terminal.Gui/App/SessionToken.cs | 2 +- Terminal.Gui/App/Timeout/ITimedEvents.cs | 11 +- .../App/Timeout/LogarithmicTimeout.cs | 1 + .../App/Timeout/SmoothAcceleratingTimeout.cs | 1 + Terminal.Gui/App/Timeout/TimedEvents.cs | 28 +- Terminal.Gui/App/Timeout/Timeout.cs | 2 +- .../Toplevel/IToplevelTransitionManager.cs | 8 +- .../App/Toplevel/ToplevelTransitionManager.cs | 15 +- .../Configuration/AppSettingsScope.cs | 3 +- .../Configuration/AttributeJsonConverter.cs | 4 +- .../Configuration/ColorJsonConverter.cs | 2 +- .../ConcurrentDictionaryJsonConverter.cs | 1 + Terminal.Gui/Configuration/ConfigLocations.cs | 3 +- Terminal.Gui/Configuration/ConfigProperty.cs | 3 +- .../Configuration/ConfigurationManager.cs | 4 +- .../ConfigurationManagerEventArgs.cs | 4 +- ...ConfigurationManagerNotEnabledException.cs | 3 +- .../ConfigurationPropertyAttribute.cs | 4 +- Terminal.Gui/Configuration/DeepCloner.cs | 2 +- .../Configuration/DictionaryJsonConverter.cs | 3 +- .../Configuration/KeyCodeJsonConverter.cs | 3 +- .../Configuration/KeyJsonConverter.cs | 2 +- .../Configuration/RuneJsonConverter.cs | 2 +- .../Configuration/SchemeJsonConverter.cs | 3 +- Terminal.Gui/Configuration/SchemeManager.cs | 3 +- Terminal.Gui/Configuration/Scope.cs | 3 +- .../Configuration/ScopeJsonConverter.cs | 3 +- Terminal.Gui/Configuration/SettingsScope.cs | 3 +- Terminal.Gui/Configuration/SourcesManager.cs | 3 +- Terminal.Gui/Configuration/ThemeManager.cs | 3 +- Terminal.Gui/Configuration/ThemeScope.cs | 3 +- Terminal.Gui/Drawing/Attribute.cs | 3 +- Terminal.Gui/Drawing/Cell.cs | 3 +- Terminal.Gui/Drawing/Color/AnsiColorCode.cs | 1 + .../Drawing/Color/AnsiColorNameResolver.cs | 2 - .../Drawing/Color/Color.ColorExtensions.cs | 1 - .../Color/Color.ColorParseException.cs | 1 - .../Drawing/Color/Color.Formatting.cs | 1 - Terminal.Gui/Drawing/Color/Color.Operators.cs | 1 - Terminal.Gui/Drawing/Color/Color.cs | 1 - Terminal.Gui/Drawing/Color/ColorModel.cs | 2 - Terminal.Gui/Drawing/Color/ColorStrings.cs | 1 - .../Drawing/Color/IColorNameResolver.cs | 2 - .../Drawing/Color/ICustomColorFormatter.cs | 1 - .../Color/MultiStandardColorNameResolver.cs | 2 - Terminal.Gui/Drawing/Color/StandardColors.cs | 1 - .../Color/StandardColorsNameResolver.cs | 4 +- Terminal.Gui/Drawing/Glyphs.cs | 3 +- .../LineCanvas/IntersectionDefinition.cs | 2 +- .../LineCanvas/IntersectionRuneType.cs | 2 +- .../Drawing/LineCanvas/IntersectionType.cs | 2 +- Terminal.Gui/Drawing/LineCanvas/LineCanvas.cs | 15 +- Terminal.Gui/Drawing/LineCanvas/LineStyle.cs | 1 - .../Drawing/LineCanvas/StraightLine.cs | 1 - .../LineCanvas/StraightLineExtensions.cs | 6 +- .../Quant/PopularityPaletteWithThreshold.cs | 19 +- Terminal.Gui/Drawing/Region.cs | 63 +- Terminal.Gui/Drawing/RegionOp.cs | 3 +- Terminal.Gui/Drawing/Ruler.cs | 11 +- Terminal.Gui/Drawing/Scheme.cs | 1 - .../Drawing/Sixel/SixelSupportDetector.cs | 25 +- Terminal.Gui/Drawing/Sixel/SixelToRender.cs | 2 +- Terminal.Gui/Drawing/Thickness.cs | 19 +- Terminal.Gui/Drawing/VisualRoleEventArgs.cs | 5 +- .../AnsiHandling/AnsiEscapeSequence.cs | 1 - .../AnsiHandling/AnsiEscapeSequenceRequest.cs | 6 +- .../Drivers/AnsiHandling/AnsiMouseParser.cs | 1 - .../AnsiHandling/AnsiRequestScheduler.cs | 19 +- .../AnsiHandling/AnsiResponseExpectation.cs | 1 - .../AnsiHandling/AnsiResponseParser.cs | 2 - .../EscSeqUtils/EscSeqReqStatus.cs | 1 - .../EscSeqUtils/EscSeqRequests.cs | 2 - .../AnsiHandling/EscSeqUtils/EscSeqUtils.cs | 1 - .../Drivers/AnsiHandling/GenericHeld.cs | 1 - .../AnsiHandling/IAnsiResponseParser.cs | 1 - Terminal.Gui/Drivers/AnsiHandling/IHeld.cs | 1 - .../Keyboard/AnsiKeyboardParser.cs | 1 - .../Keyboard/AnsiKeyboardParserPattern.cs | 2 - .../AnsiHandling/Keyboard/CsiCursorPattern.cs | 1 - .../AnsiHandling/Keyboard/CsiKeyPattern.cs | 1 - .../AnsiHandling/Keyboard/EscAsAltPattern.cs | 1 - .../AnsiHandling/Keyboard/Ss3Pattern.cs | 1 - .../Drivers/AnsiHandling/Osc8UrlLinker.cs | 1 - .../Drivers/AnsiHandling/ReasonCannotSend.cs | 1 - .../Drivers/AnsiHandling/StringHeld.cs | 1 - Terminal.Gui/Drivers/ComponentFactoryImpl.cs | 3 +- Terminal.Gui/Drivers/CursorVisibility.cs | 1 - .../DotNetDriver/NetComponentFactory.cs | 1 - Terminal.Gui/Drivers/DotNetDriver/NetInput.cs | 3 +- .../Drivers/DotNetDriver/NetOutput.cs | 2 - .../Drivers/DotNetDriver/NetWinVTConsole.cs | 1 - Terminal.Gui/Drivers/DriverImpl.cs | 304 +++---- .../Drivers/FakeDriver/FakeClipboard.cs | 1 - .../FakeDriver/FakeComponentFactory.cs | 1 - Terminal.Gui/Drivers/FakeDriver/FakeInput.cs | 3 +- .../Drivers/FakeDriver/FakeInputProcessor.cs | 3 +- Terminal.Gui/Drivers/FakeDriver/FakeOutput.cs | 26 +- Terminal.Gui/Drivers/IComponentFactory.cs | 3 +- Terminal.Gui/Drivers/IDriver.cs | 15 +- Terminal.Gui/Drivers/IInput.cs | 3 +- Terminal.Gui/Drivers/IInputProcessor.cs | 3 +- Terminal.Gui/Drivers/IOutput.cs | 8 + Terminal.Gui/Drivers/IOutputBuffer.cs | 3 +- Terminal.Gui/Drivers/ISizeMonitor.cs | 3 +- Terminal.Gui/Drivers/InputImpl.cs | 5 +- Terminal.Gui/Drivers/InputProcessorImpl.cs | 3 +- Terminal.Gui/Drivers/KeyCode.cs | 1 - Terminal.Gui/Drivers/MouseButtonStateEx.cs | 3 +- Terminal.Gui/Drivers/MouseInterpreter.cs | 3 +- Terminal.Gui/Drivers/OutputBase.cs | 131 ++- Terminal.Gui/Drivers/OutputBufferImpl.cs | 3 +- Terminal.Gui/Drivers/Platform.cs | 2 +- Terminal.Gui/Drivers/SizeMonitorImpl.cs | 3 +- .../Drivers/UnixDriver/UnixClipboard.cs | 1 + .../UnixDriver/UnixComponentFactory.cs | 1 - Terminal.Gui/Drivers/UnixDriver/UnixInput.cs | 1 - .../Drivers/UnixDriver/UnixKeyConverter.cs | 2 - Terminal.Gui/Drivers/UnixDriver/UnixOutput.cs | 2 +- .../Drivers/WindowsDriver/ClipboardImpl.cs | 1 - .../WindowsDriver/WindowsComponentFactory.cs | 1 - .../Drivers/WindowsDriver/WindowsConsole.cs | 1 - .../Drivers/WindowsDriver/WindowsInput.cs | 1 - .../WindowsDriver/WindowsInputProcessor.cs | 1 - .../WindowsDriver/WindowsKeyConverter.cs | 2 - .../Drivers/WindowsDriver/WindowsKeyHelper.cs | 1 - .../WindowsDriver/WindowsKeyboardLayout.cs | 1 + .../Drivers/WindowsDriver/WindowsOutput.cs | 20 +- .../FileServices/DefaultSearchMatcher.cs | 8 +- .../FileServices/FileSystemIconProvider.cs | 1 - .../FileServices/FileSystemInfoStats.cs | 3 +- .../FileServices/FileSystemTreeBuilder.cs | 3 +- Terminal.Gui/Input/Command.cs | 2 +- Terminal.Gui/Input/CommandContext.cs | 5 +- Terminal.Gui/Input/CommandEventArgs.cs | 3 +- Terminal.Gui/Input/ICommandContext.cs | 3 +- Terminal.Gui/Input/IInputBinding.cs | 3 +- Terminal.Gui/Input/InputBindings.cs | 3 +- Terminal.Gui/Input/Keyboard/Key.cs | 1 - Terminal.Gui/Input/Keyboard/KeyBinding.cs | 2 +- Terminal.Gui/Input/Keyboard/KeyBindings.cs | 2 +- .../Input/Keyboard/KeyEqualityComparer.cs | 1 - .../Input/Mouse/GrabMouseEventArgs.cs | 1 - Terminal.Gui/Input/Mouse/MouseBinding.cs | 2 +- Terminal.Gui/Input/Mouse/MouseBindings.cs | 2 +- Terminal.Gui/Input/Mouse/MouseEventArgs.cs | 2 +- Terminal.Gui/Resources/GlobalResources.cs | 3 +- .../Resources/ResourceManagerWrapper.cs | 3 +- Terminal.Gui/Terminal.Gui.csproj | 11 + Terminal.Gui/Text/NerdFonts.cs | 7 +- Terminal.Gui/Text/RuneExtensions.cs | 3 +- Terminal.Gui/Text/StringExtensions.cs | 3 +- Terminal.Gui/Text/TextDirection.cs | 2 +- Terminal.Gui/Text/TextFormatter.cs | 36 +- Terminal.Gui/ViewBase/Adornment/Adornment.cs | 13 +- .../ViewBase/Adornment/Border.Arrangment.cs | 36 +- Terminal.Gui/ViewBase/Adornment/Border.cs | 32 +- .../ViewBase/Adornment/BorderSettings.cs | 3 - Terminal.Gui/ViewBase/Adornment/Margin.cs | 12 +- Terminal.Gui/ViewBase/Adornment/Padding.cs | 2 +- Terminal.Gui/ViewBase/Adornment/ShadowView.cs | 8 +- .../ViewBase/DrawAdornmentsEventArgs.cs | 3 +- Terminal.Gui/ViewBase/DrawContext.cs | 3 +- Terminal.Gui/ViewBase/DrawEventArgs.cs | 3 +- .../EnumExtensions/AddOrSubtractExtensions.cs | 1 - .../EnumExtensions/AlignmentExtensions.cs | 1 - .../AlignmentModesExtensions.cs | 1 - .../BorderSettingsExtensions.cs | 2 +- .../EnumExtensions/DimAutoStyleExtensions.cs | 1 - .../DimPercentModeExtensions.cs | 1 - .../EnumExtensions/DimensionExtensions.cs | 1 - .../ViewBase/EnumExtensions/SideExtensions.cs | 1 - .../ViewDiagnosticFlagsExtensions.cs | 1 - .../ViewBase/Helpers/StackExtensions.cs | 1 + Terminal.Gui/ViewBase/IDesignable.cs | 6 +- Terminal.Gui/ViewBase/IMouseHeldDown.cs | 3 +- Terminal.Gui/ViewBase/Layout/Aligner.cs | 1 + Terminal.Gui/ViewBase/Layout/Dim.cs | 11 +- Terminal.Gui/ViewBase/Layout/DimAbsolute.cs | 1 - Terminal.Gui/ViewBase/Layout/DimAuto.cs | 5 +- Terminal.Gui/ViewBase/Layout/DimAutoStyle.cs | 4 +- Terminal.Gui/ViewBase/Layout/DimCombine.cs | 1 - Terminal.Gui/ViewBase/Layout/DimFill.cs | 1 - Terminal.Gui/ViewBase/Layout/DimFunc.cs | 1 - Terminal.Gui/ViewBase/Layout/DimPercent.cs | 1 - .../ViewBase/Layout/DimPercentMode.cs | 4 +- Terminal.Gui/ViewBase/Layout/DimView.cs | 1 - Terminal.Gui/ViewBase/Layout/Dimension.cs | 4 +- .../ViewBase/Layout/LayoutException.cs | 1 - Terminal.Gui/ViewBase/Layout/Pos.cs | 11 +- Terminal.Gui/ViewBase/Layout/PosAbsolute.cs | 1 - Terminal.Gui/ViewBase/Layout/PosAlign.cs | 1 - Terminal.Gui/ViewBase/Layout/PosAnchorEnd.cs | 1 - Terminal.Gui/ViewBase/Layout/PosCenter.cs | 1 - Terminal.Gui/ViewBase/Layout/PosCombine.cs | 1 - Terminal.Gui/ViewBase/Layout/PosFunc.cs | 1 - Terminal.Gui/ViewBase/Layout/PosPercent.cs | 1 - Terminal.Gui/ViewBase/Layout/PosView.cs | 1 - Terminal.Gui/ViewBase/Layout/Side.cs | 4 +- .../Layout/SuperViewChangedEventArgs.cs | 6 +- Terminal.Gui/ViewBase/MouseHeldDown.cs | 3 +- .../ViewBase/Navigation/FocusEventArgs.cs | 9 +- .../ViewBase/Orientation/IOrientation.cs | 4 +- .../ViewBase/Orientation/Orientation.cs | 2 +- .../ViewBase/Orientation/OrientationHelper.cs | 2 +- Terminal.Gui/ViewBase/View.Adornments.cs | 3 +- Terminal.Gui/ViewBase/View.Arrangement.cs | 3 +- Terminal.Gui/ViewBase/View.Command.cs | 3 +- Terminal.Gui/ViewBase/View.Content.cs | 3 +- Terminal.Gui/ViewBase/View.Cursor.cs | 1 - Terminal.Gui/ViewBase/View.Diagnostics.cs | 3 +- .../ViewBase/View.Drawing.Attribute.cs | 3 +- .../ViewBase/View.Drawing.Clipping.cs | 27 +- .../ViewBase/View.Drawing.Primitives.cs | 10 +- Terminal.Gui/ViewBase/View.Drawing.Scheme.cs | 3 +- Terminal.Gui/ViewBase/View.Drawing.cs | 20 +- Terminal.Gui/ViewBase/View.Hierarchy.cs | 5 +- Terminal.Gui/ViewBase/View.Keyboard.cs | 3 +- Terminal.Gui/ViewBase/View.Layout.cs | 57 +- Terminal.Gui/ViewBase/View.Mouse.cs | 23 +- Terminal.Gui/ViewBase/View.Navigation.cs | 27 +- Terminal.Gui/ViewBase/View.ScrollBars.cs | 3 +- Terminal.Gui/ViewBase/View.Text.cs | 1 - Terminal.Gui/ViewBase/View.cs | 61 +- Terminal.Gui/ViewBase/ViewDiagnosticFlags.cs | 3 +- Terminal.Gui/ViewBase/ViewEventArgs.cs | 2 +- .../Views/Autocomplete/AppendAutocomplete.cs | 35 +- .../Views/Autocomplete/AutocompleteBase.cs | 1 + .../Views/Autocomplete/AutocompleteContext.cs | 1 + .../AutocompleteFilepathContext.cs | 1 + .../Views/Autocomplete/IAutocomplete.cs | 1 + .../Autocomplete/ISuggestionGenerator.cs | 1 + .../Autocomplete/PopupAutocomplete.PopUp.cs | 1 - .../Views/Autocomplete/PopupAutocomplete.cs | 10 +- .../SingleWordSuggestionGenerator.cs | 1 + Terminal.Gui/Views/Autocomplete/Suggestion.cs | 1 + Terminal.Gui/Views/Bar.cs | 19 +- Terminal.Gui/Views/Button.cs | 1 + Terminal.Gui/Views/CharMap/CharMap.cs | 7 +- Terminal.Gui/Views/CharMap/UcdApiClient.cs | 1 - Terminal.Gui/Views/CharMap/UnicodeRange.cs | 1 - Terminal.Gui/Views/CheckBox.cs | 2 +- Terminal.Gui/Views/CheckState.cs | 2 +- .../CollectionNavigator.cs | 3 +- .../CollectionNavigatorBase.cs | 4 +- .../DefaultCollectionNavigatorMatcher.cs | 2 +- .../ICollectionNavigator.cs | 2 +- .../ICollectionNavigatorMatcher.cs | 1 + .../IListCollectionNavigator.cs | 1 + .../TableCollectionNavigator.cs | 1 + Terminal.Gui/Views/Color/BBar.cs | 2 +- Terminal.Gui/Views/Color/ColorBar.cs | 2 +- .../Views/Color/ColorModelStrategy.cs | 2 +- Terminal.Gui/Views/Color/ColorPicker.16.cs | 2 +- .../Views/Color/ColorPicker.Prompt.cs | 1 + Terminal.Gui/Views/Color/ColorPicker.Style.cs | 2 +- Terminal.Gui/Views/Color/ColorPicker.cs | 2 +- Terminal.Gui/Views/Color/GBar.cs | 2 +- Terminal.Gui/Views/Color/HueBar.cs | 2 +- Terminal.Gui/Views/Color/IColorBar.cs | 1 + Terminal.Gui/Views/Color/LightnessBar.cs | 2 +- Terminal.Gui/Views/Color/RBar.cs | 2 +- Terminal.Gui/Views/Color/SaturationBar.cs | 2 +- Terminal.Gui/Views/Color/ValueBar.cs | 2 +- Terminal.Gui/Views/ComboBox.cs | 1 + Terminal.Gui/Views/DatePicker.cs | 2 +- Terminal.Gui/Views/Dialog.cs | 6 +- Terminal.Gui/Views/FileDialog.cs | 3 +- Terminal.Gui/Views/FileDialogs/AllowedType.cs | 1 + .../FileDialogs/DefaultFileOperations.cs | 1 + Terminal.Gui/Views/FileDialogs/FileDialog.cs | 17 +- .../FileDialogCollectionNavigator.cs | 1 + .../Views/FileDialogs/FileDialogHistory.cs | 1 + .../Views/FileDialogs/FileDialogState.cs | 1 + .../Views/FileDialogs/FileDialogStyle.cs | 1 + .../FileDialogs/FileDialogTableSource.cs | 2 +- .../FileDialogs/FilesSelectedEventArgs.cs | 1 + Terminal.Gui/Views/FileDialogs/OpenDialog.cs | 3 +- Terminal.Gui/Views/FileDialogs/OpenMode.cs | 1 + Terminal.Gui/Views/FileDialogs/SaveDialog.cs | 3 +- Terminal.Gui/Views/FrameView.cs | 2 +- Terminal.Gui/Views/GraphView/Axis.cs | 1 + Terminal.Gui/Views/GraphView/BarSeriesBar.cs | 1 + .../Views/GraphView/GraphCellToRender.cs | 1 + Terminal.Gui/Views/GraphView/GraphView.cs | 5 +- Terminal.Gui/Views/GraphView/IAnnotation.cs | 1 + .../Views/GraphView/LegendAnnotation.cs | 1 + Terminal.Gui/Views/GraphView/LineF.cs | 1 + .../Views/GraphView/PathAnnotation.cs | 1 + Terminal.Gui/Views/GraphView/Series.cs | 1 - .../Views/GraphView/TextAnnotation.cs | 1 + Terminal.Gui/Views/HexView.cs | 2 +- Terminal.Gui/Views/HexViewEventArgs.cs | 1 + Terminal.Gui/Views/IListDataSource.cs | 2 +- Terminal.Gui/Views/Label.cs | 6 +- Terminal.Gui/Views/Line.cs | 2 - Terminal.Gui/Views/ListView.cs | 1 + Terminal.Gui/Views/ListViewEventArgs.cs | 1 + Terminal.Gui/Views/Menu/MenuBarItemv2.cs | 5 +- Terminal.Gui/Views/Menu/MenuBarv2.cs | 43 +- Terminal.Gui/Views/Menu/MenuItemv2.cs | 4 +- Terminal.Gui/Views/Menu/Menuv2.cs | 4 +- Terminal.Gui/Views/Menu/PopoverMenu.cs | 34 +- Terminal.Gui/Views/Menuv1/Menu.cs | 23 +- Terminal.Gui/Views/Menuv1/MenuBar.cs | 13 +- Terminal.Gui/Views/Menuv1/MenuBarItem.cs | 1 - .../Views/Menuv1/MenuClosingEventArgs.cs | 3 +- Terminal.Gui/Views/Menuv1/MenuItem.cs | 1 - .../Views/Menuv1/MenuItemCheckStyle.cs | 3 +- .../Views/Menuv1/MenuOpenedEventArgs.cs | 3 +- .../Views/Menuv1/MenuOpeningEventArgs.cs | 3 +- Terminal.Gui/Views/MessageBox.cs | 1 + Terminal.Gui/Views/NumericUpDown.cs | 1 - Terminal.Gui/Views/ProgressBar.cs | 14 +- .../Views/ReadOnlyCollectionExtensions.cs | 1 + Terminal.Gui/Views/ScrollBar/ScrollBar.cs | 2 +- Terminal.Gui/Views/ScrollBar/ScrollSlider.cs | 2 +- Terminal.Gui/Views/SelectedItemChangedArgs.cs | 2 +- Terminal.Gui/Views/Selectors/FlagSelector.cs | 4 - .../Views/Selectors/FlagSelectorTEnum.cs | 1 - .../Views/Selectors/OptionSelector.cs | 1 - .../Views/Selectors/OptionSelectorTEnum.cs | 1 - Terminal.Gui/Views/Selectors/SelectorBase.cs | 1 - .../Views/Selectors/SelectorStyles.cs | 2 +- Terminal.Gui/Views/Shortcut.cs | 108 ++- Terminal.Gui/Views/Slider/Slider.cs | 5 +- Terminal.Gui/Views/Slider/SliderAttributes.cs | 2 +- .../Views/Slider/SliderConfiguration.cs | 4 +- Terminal.Gui/Views/Slider/SliderEventArgs.cs | 3 +- Terminal.Gui/Views/Slider/SliderOption.cs | 3 +- .../Views/Slider/SliderOptionEventArgs.cs | 3 +- Terminal.Gui/Views/Slider/SliderStyle.cs | 2 +- Terminal.Gui/Views/Slider/SliderType.cs | 2 +- Terminal.Gui/Views/SpinnerView/SpinnerView.cs | 12 +- Terminal.Gui/Views/StatusBar.cs | 19 +- Terminal.Gui/Views/TabView/Tab.cs | 2 +- .../Views/TabView/TabChangedEventArgs.cs | 1 + .../Views/TabView/TabMouseEventArgs.cs | 2 +- Terminal.Gui/Views/TabView/TabRow.cs | 1 - Terminal.Gui/Views/TabView/TabStyle.cs | 1 + Terminal.Gui/Views/TabView/TabView.cs | 1 - .../Views/TableView/CellActivatedEventArgs.cs | 1 + .../Views/TableView/CellColorGetterArgs.cs | 1 + .../Views/TableView/CellToggledEventArgs.cs | 1 + .../TableView/CheckBoxTableSourceWrapper.cs | 1 + .../CheckBoxTableSourceWrapperByIndex.cs | 1 + .../CheckBoxTableSourceWrapperByObject.cs | 1 + Terminal.Gui/Views/TableView/ColumnStyle.cs | 1 + .../Views/TableView/DataTableSource.cs | 1 + .../Views/TableView/EnumerableTableSource.cs | 1 + .../Views/TableView/IEnumerableTableSource.cs | 1 + Terminal.Gui/Views/TableView/ITableSource.cs | 1 + .../Views/TableView/ListColumnStyle.cs | 2 +- .../Views/TableView/ListTableSource.cs | 1 + .../Views/TableView/RowColorGetterArgs.cs | 1 + .../TableView/SelectedCellChangedEventArgs.cs | 1 + .../Views/TableView/TableSelection.cs | 1 + Terminal.Gui/Views/TableView/TableStyle.cs | 1 + Terminal.Gui/Views/TableView/TableView.cs | 1 + .../Views/TableView/TreeTableSource.cs | 1 + .../TextInput/ContentsChangedEventArgs.cs | 1 + Terminal.Gui/Views/TextInput/DateField.cs | 2 +- Terminal.Gui/Views/TextInput/HistoryText.cs | 1 - .../TextInput/HistoryTextItemEventArgs.cs | 2 +- .../Views/TextInput/ITextValidateProvider.cs | 2 +- .../Views/TextInput/NetMaskedTextProvider.cs | 3 +- .../Views/TextInput/TextEditingLineStatus.cs | 1 + Terminal.Gui/Views/TextInput/TextField.cs | 32 +- Terminal.Gui/Views/TextInput/TextModel.cs | 1 - .../Views/TextInput/TextRegexProvider.cs | 2 +- .../Views/TextInput/TextValidateField.cs | 2 +- Terminal.Gui/Views/TextInput/TextView.cs | 34 +- Terminal.Gui/Views/TextInput/TimeField.cs | 1 + .../Views/TextInput/WordWrapManager.cs | 1 - Terminal.Gui/Views/Toplevel.cs | 34 +- Terminal.Gui/Views/ToplevelEventArgs.cs | 1 + .../Views/TreeView/AspectGetterDelegate.cs | 1 + Terminal.Gui/Views/TreeView/Branch.cs | 2 +- .../Views/TreeView/DelegateTreeBuilder.cs | 1 + .../TreeView/DrawTreeViewLineEventArgs.cs | 1 + Terminal.Gui/Views/TreeView/ITreeBuilder.cs | 1 + .../Views/TreeView/ITreeViewFilter.cs | 1 + .../TreeView/ObjectActivatedEventArgs.cs | 1 + .../TreeView/SelectionChangedEventArgs.cs | 1 + Terminal.Gui/Views/TreeView/TreeBuilder.cs | 1 + Terminal.Gui/Views/TreeView/TreeNode.cs | 1 + .../Views/TreeView/TreeNodeBuilder.cs | 1 + Terminal.Gui/Views/TreeView/TreeStyle.cs | 1 + Terminal.Gui/Views/TreeView/TreeView.cs | 1 + .../Views/TreeView/TreeViewTextFilter.cs | 1 + Terminal.Gui/Views/View.cs | 1 - Terminal.Gui/Views/Window.cs | 2 +- Terminal.Gui/Views/Wizard/Wizard.cs | 8 +- Terminal.Gui/Views/Wizard/WizardEventArgs.cs | 1 + Terminal.Gui/Views/Wizard/WizardStep.cs | 2 +- Terminal.sln | 37 +- .../FluentTests/FileDialogFluentTests.cs | 6 +- .../GuiTestContextKeyEventTests.cs | 36 +- .../GuiTestContextMouseEventTests.cs | 2 +- .../FluentTests/GuiTestContextTests.cs | 20 +- .../FluentTests/MenuBarv2Tests.cs | 156 ++-- .../FluentTests/NavigationTests.cs | 6 +- .../FluentTests/PopverMenuTests.cs | 171 ++-- .../FluentTests/TreeViewFluentTests.cs | 4 +- .../UICatalog/ScenarioTests.cs | 39 +- Tests/StressTests/ApplicationStressTests.cs | 4 +- Tests/StressTests/ScenariosStressTests.cs | 4 +- .../FakeDriver/FakeApplicationFactory.cs | 9 +- .../FakeDriver/FakeApplicationLifecycle.cs | 6 +- .../GuiTestContext.ContextMenu.cs | 10 +- .../GuiTestContext.Input.cs | 16 +- .../GuiTestContext.Navigation.cs | 6 +- .../GuiTestContext.ViewBase.cs | 10 +- .../GuiTestContext.cs | 111 +-- .../Application.NavigationTests.cs | 58 +- .../ApplicationImplBeginEndTests.cs | 505 ++++++++++++ .../Application/ApplicationImplTests.cs | 522 ++++-------- .../Application/ApplicationPopoverTests.cs | 82 +- .../Application/ApplicationScreenTests.cs | 12 +- .../UnitTests/Application/ApplicationTests.cs | 275 +++---- Tests/UnitTests/Application/CursorTests.cs | 34 +- .../Application/MainLoopCoordinatorTests.cs | 2 +- .../Mouse/ApplicationMouseEnterLeaveTests.cs | 78 +- .../Mouse/ApplicationMouseTests.cs | 16 +- .../Application/SessionTokenTests.cs | 15 +- .../Application/SynchronizatonContextTests.cs | 8 +- .../UnitTests/Application/TimedEventsTests.cs | 31 + Tests/UnitTests/AutoInitShutdownAttribute.cs | 28 +- .../Configuration/ThemeScopeTests.cs | 37 + Tests/UnitTests/Dialogs/DialogTests.cs | 30 +- Tests/UnitTests/Dialogs/MessageBoxTests.cs | 14 +- Tests/UnitTests/DriverAssert.cs | 10 +- Tests/UnitTests/Drivers/ClipRegionTests.cs | 6 +- Tests/UnitTests/Drivers/DriverTests.cs | 38 +- .../SetupFakeApplicationAttribute.cs | 1 + .../View/Adornment/AdornmentSubViewTests.cs | 26 +- .../View/Adornment/AdornmentTests.cs | 8 +- Tests/UnitTests/View/Adornment/BorderTests.cs | 16 +- Tests/UnitTests/View/Adornment/MarginTests.cs | 32 +- .../UnitTests/View/Adornment/PaddingTests.cs | 8 +- .../View/Adornment/ShadowStyleTests.cs | 9 +- Tests/UnitTests/View/ArrangementTests.cs | 217 +++++ .../UnitTests/View/Draw/ClearViewportTests.cs | 20 +- Tests/UnitTests/View/Draw/ClipTests.cs | 33 +- Tests/UnitTests/View/Draw/DrawTests.cs | 40 +- Tests/UnitTests/View/Draw/TransparentTests.cs | 2 + .../View/Keyboard/KeyBindingsTests.cs | 6 +- .../View/Layout/GetViewsUnderLocationTests.cs | 226 +++--- .../UnitTests/View/Layout/Pos.CombineTests.cs | 26 +- Tests/UnitTests/View/Layout/Pos.Tests.cs | 10 +- Tests/UnitTests/View/Layout/SetLayoutTests.cs | 16 +- .../View/Navigation/CanFocusTests.cs | 21 +- .../View/Navigation/NavigationTests.cs | 13 +- Tests/UnitTests/View/TextTests.cs | 11 +- Tests/UnitTests/View/ViewCommandTests.cs | 12 +- Tests/UnitTests/View/ViewTests.cs | 1 + .../ViewportSettings.TransparentMouseTests.cs | 6 +- .../Views/AppendAutocompleteTests.cs | 86 +- Tests/UnitTests/Views/ButtonTests.cs | 17 +- Tests/UnitTests/Views/CheckBoxTests.cs | 9 +- Tests/UnitTests/Views/ColorPickerTests.cs | 12 +- Tests/UnitTests/Views/ComboBoxTests.cs | 19 +- Tests/UnitTests/Views/GraphViewTests.cs | 26 +- Tests/UnitTests/Views/HexViewTests.cs | 78 +- Tests/UnitTests/Views/LabelTests.cs | 88 +- Tests/UnitTests/Views/MenuBarTests.cs | 60 +- .../UnitTests/Views/Menuv1/MenuBarv1Tests.cs | 102 +-- Tests/UnitTests/Views/ProgressBarTests.cs | 25 +- Tests/UnitTests/Views/ScrollBarTests.cs | 2 + Tests/UnitTests/Views/ScrollSliderTests.cs | 340 -------- Tests/UnitTests/Views/ShortcutTests.cs | 83 +- Tests/UnitTests/Views/SpinnerViewTests.cs | 8 +- Tests/UnitTests/Views/TabViewTests.cs | 50 +- Tests/UnitTests/Views/TableViewTests.cs | 171 ++-- Tests/UnitTests/Views/TextFieldTests.cs | 44 +- Tests/UnitTests/Views/TextViewTests.cs | 20 +- Tests/UnitTests/Views/ToplevelTests.cs | 72 +- Tests/UnitTests/Views/TreeTableSourceTests.cs | 19 +- Tests/UnitTests/Views/TreeViewTests.cs | 50 +- .../Application/ApplicationPopoverTests.cs | 12 +- .../Application/MouseInterfaceTests.cs | 2 +- .../Application/MouseTests.cs | 2 +- .../Application/PopoverBaseImplTests.cs | 8 +- .../Configuration/SourcesManagerTests.cs | 27 + .../Drawing/RulerTests.cs | 14 +- .../Drawing/ThicknessTests.cs | 21 +- .../Drivers/AnsiRequestSchedulerTests.cs | 30 +- .../Drivers/ToAnsiTests.cs | 342 ++++++++ Tests/UnitTestsParallelizable/TestSetup.cs | 8 +- .../Text/TextFormatterDrawTests.cs | 36 +- .../Text/TextFormatterJustificationTests.cs | 2 +- .../Text/TextFormatterTests.cs | 31 +- .../UnitTests.Parallelizable.csproj | 3 + .../View/ArrangementTests.cs | 454 +++-------- .../View/Draw/NeedsDrawTests.cs | 16 +- .../View/Draw/ViewClearViewportTests.cs | 371 +++++++++ .../Draw/ViewDrawTextAndLineCanvasTests.cs | 495 ++++++++++++ .../View/Draw/ViewDrawingClippingTests.cs | 754 ++++++++++++++++++ .../View/Draw/ViewDrawingFlowTests.cs | 701 ++++++++++++++++ .../View/Layout/GetViewsUnderLocationTests.cs | 4 +- .../Views/AllViewsDrawTests.cs | 1 + .../Views/LineTests.cs | 12 +- .../Views/ScrollSliderTests.cs | 341 +++++++- .../Views/ShortcutTests.cs | 13 +- codecov.yml | 2 +- docfx/docs/View.md | 5 + docfx/docs/application.md | 552 +++++++++++++ docfx/docs/config.md | 2 +- docfx/docs/drawing.md | 40 +- docfx/docs/drivers.md | 110 +-- docfx/docs/keyboard.md | 50 +- docfx/docs/migratingfromv1.md | 6 +- docfx/docs/mouse.md | 29 +- docfx/docs/navigation.md | 2 +- docfx/docs/toc.yml | 2 + docfx/schemas/tui-config-schema.json | 2 +- 591 files changed, 8850 insertions(+), 4437 deletions(-) create mode 100644 NULLABLE_VIEWS_REMAINING.md create mode 100644 PR_DESCRIPTION_UPDATED.md create mode 100644 Terminal.Gui/App/Application.Current.cs delete mode 100644 Terminal.Gui/App/Application.Toplevel.cs delete mode 100644 Terminal.Gui/Views/View.cs create mode 100644 Tests/UnitTests/Application/ApplicationImplBeginEndTests.cs create mode 100644 Tests/UnitTests/View/ArrangementTests.cs delete mode 100644 Tests/UnitTests/Views/ScrollSliderTests.cs create mode 100644 Tests/UnitTestsParallelizable/Drivers/ToAnsiTests.cs create mode 100644 Tests/UnitTestsParallelizable/View/Draw/ViewClearViewportTests.cs create mode 100644 Tests/UnitTestsParallelizable/View/Draw/ViewDrawTextAndLineCanvasTests.cs create mode 100644 Tests/UnitTestsParallelizable/View/Draw/ViewDrawingClippingTests.cs create mode 100644 Tests/UnitTestsParallelizable/View/Draw/ViewDrawingFlowTests.cs create mode 100644 docfx/docs/application.md diff --git a/Examples/CommunityToolkitExample/Program.cs b/Examples/CommunityToolkitExample/Program.cs index 265c979aa..74e45ce06 100644 --- a/Examples/CommunityToolkitExample/Program.cs +++ b/Examples/CommunityToolkitExample/Program.cs @@ -16,7 +16,7 @@ public static class Program Services = ConfigureServices (); Application.Init (); Application.Run (Services.GetRequiredService ()); - Application.Top?.Dispose (); + Application.Current?.Dispose (); Application.Shutdown (); } diff --git a/Examples/ReactiveExample/Program.cs b/Examples/ReactiveExample/Program.cs index 910d1f4a4..f73aa807d 100644 --- a/Examples/ReactiveExample/Program.cs +++ b/Examples/ReactiveExample/Program.cs @@ -16,7 +16,7 @@ public static class Program RxApp.MainThreadScheduler = TerminalScheduler.Default; RxApp.TaskpoolScheduler = TaskPoolScheduler.Default; Application.Run (new LoginView (new LoginViewModel ())); - Application.Top.Dispose (); + Application.Current.Dispose (); Application.Shutdown (); } } diff --git a/Examples/ReactiveExample/TerminalScheduler.cs b/Examples/ReactiveExample/TerminalScheduler.cs index 9c8286722..3b24cc6d8 100644 --- a/Examples/ReactiveExample/TerminalScheduler.cs +++ b/Examples/ReactiveExample/TerminalScheduler.cs @@ -22,7 +22,7 @@ public class TerminalScheduler : LocalScheduler var cancellation = new CancellationDisposable (); Application.Invoke ( - () => + (_) => { if (!cancellation.Token.IsCancellationRequested) { diff --git a/Examples/UICatalog/Resources/config.json b/Examples/UICatalog/Resources/config.json index 916743d23..e47ea567c 100644 --- a/Examples/UICatalog/Resources/config.json +++ b/Examples/UICatalog/Resources/config.json @@ -86,7 +86,7 @@ "Menu": { "Normal": { "Foreground": "Black", - "Background": "WHite" + "Background": "White" }, "Focus": { "Foreground": "White", @@ -136,17 +136,16 @@ { "UI Catalog Theme": { "Window.DefaultShadow": "Transparent", + "Button.DefaultShadow": "None", "CheckBox.DefaultHighlightStates": "In, Pressed, PressedOutside", "MessageBox.DefaultButtonAlignment": "Start", "StatusBar.DefaultSeparatorLineStyle": "Single", "Dialog.DefaultMinimumWidth": 80, - "MessageBox.DefaultBorderStyle": "Dotted", "NerdFonts.Enable": false, "MessageBox.DefaultMinimumWidth": 0, "Window.DefaultBorderStyle": "Double", "Dialog.DefaultShadow": "Opaque", "Dialog.DefaultButtonAlignment": "Start", - "Button.DefaultShadow": "Transparent", "FrameView.DefaultBorderStyle": "Double", "MessageBox.DefaultMinimumHeight": 0, "Button.DefaultHighlightStates": "In, Pressed", diff --git a/Examples/UICatalog/Scenario.cs b/Examples/UICatalog/Scenario.cs index 76fc5dc2a..0531d06c9 100644 --- a/Examples/UICatalog/Scenario.cs +++ b/Examples/UICatalog/Scenario.cs @@ -221,7 +221,7 @@ public class Scenario : IDisposable private void OnApplicationSessionBegun (object? sender, SessionTokenEventArgs e) { - SubscribeAllSubViews (Application.Top!); + SubscribeAllSubViews (Application.Current!); _demoKeys = GetDemoKeyStrokes (); @@ -241,7 +241,7 @@ public class Scenario : IDisposable return; - // Get a list of all subviews under Application.Top (and their subviews, etc.) + // Get a list of all subviews under Application.Current (and their subviews, etc.) // and subscribe to their DrawComplete event void SubscribeAllSubViews (View view) { diff --git a/Examples/UICatalog/Scenarios/AllViewsTester.cs b/Examples/UICatalog/Scenarios/AllViewsTester.cs index c3695121e..2f6e9e016 100644 --- a/Examples/UICatalog/Scenarios/AllViewsTester.cs +++ b/Examples/UICatalog/Scenarios/AllViewsTester.cs @@ -28,7 +28,7 @@ public class AllViewsTester : Scenario public override void Main () { - // Don't create a sub-win (Scenario.Win); just use Application.Top + // Don't create a sub-win (Scenario.Win); just use Application.Current Application.Init (); var app = new Window diff --git a/Examples/UICatalog/Scenarios/AnimationScenario/AnimationScenario.cs b/Examples/UICatalog/Scenarios/AnimationScenario/AnimationScenario.cs index e058ea4bf..7d1b36020 100644 --- a/Examples/UICatalog/Scenarios/AnimationScenario/AnimationScenario.cs +++ b/Examples/UICatalog/Scenarios/AnimationScenario/AnimationScenario.cs @@ -92,7 +92,7 @@ public class AnimationScenario : Scenario { // When updating from a Thread/Task always use Invoke Application.Invoke ( - () => + (_) => { _imageView.NextFrame (); _imageView.SetNeedsDraw (); diff --git a/Examples/UICatalog/Scenarios/AnsiRequestsScenario.cs b/Examples/UICatalog/Scenarios/AnsiRequestsScenario.cs index ad4f881ba..a8d7c72ad 100644 --- a/Examples/UICatalog/Scenarios/AnsiRequestsScenario.cs +++ b/Examples/UICatalog/Scenarios/AnsiRequestsScenario.cs @@ -1,6 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; +#nullable enable using System.Text; namespace UICatalog.Scenarios; @@ -9,16 +7,19 @@ namespace UICatalog.Scenarios; [ScenarioCategory ("Tests")] public sealed class AnsiEscapeSequenceRequests : Scenario { - private GraphView _graphView; + private GraphView? _graphView; - private ScatterSeries _sentSeries; - private ScatterSeries _answeredSeries; + private ScatterSeries? _sentSeries; + private ScatterSeries? _answeredSeries; private readonly List _sends = new (); private readonly object _lockAnswers = new object (); private readonly Dictionary _answers = new (); - private Label _lblSummary; + private Label? _lblSummary; + + private object? _updateTimeoutToken; + private object? _sendDarTimeoutToken; public override void Main () { @@ -32,7 +33,7 @@ public sealed class AnsiEscapeSequenceRequests : Scenario CanFocus = true }; - Tab single = new Tab (); + Tab single = new (); single.DisplayText = "Single"; single.View = BuildSingleTab (); @@ -57,6 +58,8 @@ public sealed class AnsiEscapeSequenceRequests : Scenario single.View.Dispose (); appWindow.Dispose (); + Application.RemoveTimeout (_updateTimeoutToken!); + Application.RemoveTimeout (_sendDarTimeoutToken!); // Shutdown - Calling Application.Shutdown is required. Application.Shutdown (); } @@ -70,7 +73,7 @@ public sealed class AnsiEscapeSequenceRequests : Scenario CanFocus = true }; - w.Padding.Thickness = new (1); + w!.Padding!.Thickness = new (1); var scrRequests = new List { @@ -103,7 +106,7 @@ public sealed class AnsiEscapeSequenceRequests : Scenario } var selAnsiEscapeSequenceRequestName = scrRequests [cbRequests.SelectedItem]; - AnsiEscapeSequence selAnsiEscapeSequenceRequest = null; + AnsiEscapeSequence? selAnsiEscapeSequenceRequest = null; switch (selAnsiEscapeSequenceRequestName) { @@ -163,12 +166,12 @@ public sealed class AnsiEscapeSequenceRequests : Scenario Value = string.IsNullOrEmpty (tfValue.Text) ? null : tfValue.Text }; - Application.Driver.QueueAnsiRequest ( + Application.Driver?.QueueAnsiRequest ( new () { Request = ansiEscapeSequenceRequest.Request, Terminator = ansiEscapeSequenceRequest.Terminator, - ResponseReceived = (s) => OnSuccess (s, tvResponse, tvError, tvValue, tvTerminator, lblSuccess), + ResponseReceived = (s) => OnSuccess (s!, tvResponse, tvError, tvValue, tvTerminator, lblSuccess), Abandoned = () => OnFail (tvResponse, tvError, tvValue, tvTerminator, lblSuccess) }); }; @@ -218,21 +221,21 @@ public sealed class AnsiEscapeSequenceRequests : Scenario Width = Dim.Fill () }; - Application.AddTimeout ( - TimeSpan.FromMilliseconds (1000), - () => - { - lock (_lockAnswers) - { - UpdateGraph (); + _updateTimeoutToken = Application.AddTimeout ( + TimeSpan.FromMilliseconds (1000), + () => + { + lock (_lockAnswers) + { + UpdateGraph (); - UpdateResponses (); - } + UpdateResponses (); + } - return true; - }); + return true; + }); var tv = new TextView () { @@ -266,28 +269,28 @@ public sealed class AnsiEscapeSequenceRequests : Scenario int lastSendTime = Environment.TickCount; object lockObj = new object (); - Application.AddTimeout ( - TimeSpan.FromMilliseconds (50), - () => - { - lock (lockObj) - { - if (cbDar.Value > 0) - { - int interval = 1000 / cbDar.Value; // Calculate the desired interval in milliseconds - int currentTime = Environment.TickCount; // Current system time in milliseconds + _sendDarTimeoutToken = Application.AddTimeout ( + TimeSpan.FromMilliseconds (50), + () => + { + lock (lockObj) + { + if (cbDar.Value > 0) + { + int interval = 1000 / cbDar.Value; // Calculate the desired interval in milliseconds + int currentTime = Environment.TickCount; // Current system time in milliseconds - // Check if the time elapsed since the last send is greater than the interval - if (currentTime - lastSendTime >= interval) - { - SendDar (); // Send the request - lastSendTime = currentTime; // Update the last send time - } - } - } + // Check if the time elapsed since the last send is greater than the interval + if (currentTime - lastSendTime >= interval) + { + SendDar (); // Send the request + lastSendTime = currentTime; // Update the last send time + } + } + } - return true; - }); + return true; + }); _graphView = new GraphView () @@ -318,7 +321,7 @@ public sealed class AnsiEscapeSequenceRequests : Scenario } private void UpdateResponses () { - _lblSummary.Text = GetSummary (); + _lblSummary!.Text = GetSummary (); _lblSummary.SetNeedsDraw (); } @@ -340,8 +343,8 @@ public sealed class AnsiEscapeSequenceRequests : Scenario private void SetupGraph () { - _graphView.Series.Add (_sentSeries = new ScatterSeries ()); - _graphView.Series.Add (_answeredSeries = new ScatterSeries ()); + _graphView!.Series.Add (_sentSeries = new ScatterSeries ()); + _graphView!.Series.Add (_answeredSeries = new ScatterSeries ()); _sentSeries.Fill = new GraphCellToRender (new Rune ('.'), new Attribute (ColorName16.BrightGreen, ColorName16.Black)); _answeredSeries.Fill = new GraphCellToRender (new Rune ('.'), new Attribute (ColorName16.BrightRed, ColorName16.Black)); @@ -358,17 +361,17 @@ public sealed class AnsiEscapeSequenceRequests : Scenario private void UpdateGraph () { - _sentSeries.Points = _sends + _sentSeries!.Points = _sends .GroupBy (ToSeconds) .Select (g => new PointF (g.Key, g.Count ())) .ToList (); - _answeredSeries.Points = _answers.Keys + _answeredSeries!.Points = _answers.Keys .GroupBy (ToSeconds) .Select (g => new PointF (g.Key, g.Count ())) .ToList (); // _graphView.ScrollOffset = new PointF(,0); - _graphView.SetNeedsDraw (); + _graphView!.SetNeedsDraw (); } @@ -379,13 +382,13 @@ public sealed class AnsiEscapeSequenceRequests : Scenario private void SendDar () { - Application.Driver.QueueAnsiRequest ( - new () - { - Request = EscSeqUtils.CSI_SendDeviceAttributes.Request, - Terminator = EscSeqUtils.CSI_SendDeviceAttributes.Terminator, - ResponseReceived = HandleResponse - }); + Application.Driver?.QueueAnsiRequest ( + new () + { + Request = EscSeqUtils.CSI_SendDeviceAttributes.Request, + Terminator = EscSeqUtils.CSI_SendDeviceAttributes.Terminator, + ResponseReceived = HandleResponse! + }); _sends.Add (DateTime.Now); } diff --git a/Examples/UICatalog/Scenarios/Bars.cs b/Examples/UICatalog/Scenarios/Bars.cs index 50153f07d..03f908611 100644 --- a/Examples/UICatalog/Scenarios/Bars.cs +++ b/Examples/UICatalog/Scenarios/Bars.cs @@ -28,7 +28,7 @@ public class Bars : Scenario // QuitKey and it only sticks if changed after init private void App_Loaded (object sender, EventArgs e) { - Application.Top!.Title = GetQuitKeyAndName (); + Application.Current!.Title = GetQuitKeyAndName (); ObservableCollection eventSource = new (); ListView eventLog = new ListView () @@ -41,7 +41,7 @@ public class Bars : Scenario Source = new ListWrapper (eventSource) }; eventLog.Border!.Thickness = new (0, 1, 0, 0); - Application.Top.Add (eventLog); + Application.Current.Add (eventLog); FrameView menuBarLikeExamples = new () { @@ -51,7 +51,7 @@ public class Bars : Scenario Width = Dim.Fill () - Dim.Width (eventLog), Height = Dim.Percent(33), }; - Application.Top.Add (menuBarLikeExamples); + Application.Current.Add (menuBarLikeExamples); Label label = new Label () { @@ -98,7 +98,7 @@ public class Bars : Scenario Width = Dim.Fill () - Dim.Width (eventLog), Height = Dim.Percent (33), }; - Application.Top.Add (menuLikeExamples); + Application.Current.Add (menuLikeExamples); label = new Label () { @@ -212,7 +212,7 @@ public class Bars : Scenario Width = Dim.Width (menuLikeExamples), Height = Dim.Percent (33), }; - Application.Top.Add (statusBarLikeExamples); + Application.Current.Add (statusBarLikeExamples); label = new Label () { @@ -249,7 +249,7 @@ public class Bars : Scenario ConfigStatusBar (bar); statusBarLikeExamples.Add (bar); - foreach (FrameView frameView in Application.Top.SubViews.Where (f => f is FrameView)!) + foreach (FrameView frameView in Application.Current.SubViews.Where (f => f is FrameView)!) { foreach (Bar barView in frameView.SubViews.Where (b => b is Bar)!) { @@ -269,8 +269,8 @@ public class Bars : Scenario //private void SetupContentMenu () //{ - // Application.Top.Add (new Label { Text = "Right Click for Context Menu", X = Pos.Center (), Y = 4 }); - // Application.Top.MouseClick += ShowContextMenu; + // Application.Current.Add (new Label { Text = "Right Click for Context Menu", X = Pos.Center (), Y = 4 }); + // Application.Current.MouseClick += ShowContextMenu; //} //private void ShowContextMenu (object s, MouseEventEventArgs e) diff --git a/Examples/UICatalog/Scenarios/CombiningMarks.cs b/Examples/UICatalog/Scenarios/CombiningMarks.cs index 7d8437a23..47b8dfbc7 100644 --- a/Examples/UICatalog/Scenarios/CombiningMarks.cs +++ b/Examples/UICatalog/Scenarios/CombiningMarks.cs @@ -13,7 +13,7 @@ public class CombiningMarks : Scenario top.DrawComplete += (s, e) => { // Forces reset _lineColsOffset because we're dealing with direct draw - Application.Top!.SetNeedsDraw (); + Application.Current!.SetNeedsDraw (); var i = -1; top.AddStr ("Terminal.Gui only supports combining marks that normalize. See Issue #2616."); @@ -58,9 +58,9 @@ public class CombiningMarks : Scenario top.Move (0, ++i); top.AddStr ("From now on we are using TextFormatter"); TextFormatter tf = new () { Text = "[e\u0301\u0301\u0328]<- \"[e\\u0301\\u0301\\u0328]\" using TextFormatter." }; - tf.Draw (new (0, ++i, tf.Text.Length, 1), top.GetAttributeForRole (VisualRole.Normal), top.GetAttributeForRole (VisualRole.Normal)); + tf.Draw (driver: Application.Driver, screen: new (0, ++i, tf.Text.Length, 1), normalColor: top.GetAttributeForRole (VisualRole.Normal), hotColor: top.GetAttributeForRole (VisualRole.Normal)); tf.Text = "[e\u0328\u0301]<- \"[e\\u0328\\u0301]\" using TextFormatter."; - tf.Draw (new (0, ++i, tf.Text.Length, 1), top.GetAttributeForRole (VisualRole.Normal), top.GetAttributeForRole (VisualRole.Normal)); + tf.Draw (driver: Application.Driver, screen: new (0, ++i, tf.Text.Length, 1), normalColor: top.GetAttributeForRole (VisualRole.Normal), hotColor: top.GetAttributeForRole (VisualRole.Normal)); i++; top.Move (0, ++i); top.AddStr ("From now on we are using Surrogate pairs with combining diacritics"); diff --git a/Examples/UICatalog/Scenarios/ConfigurationEditor.cs b/Examples/UICatalog/Scenarios/ConfigurationEditor.cs index a5beca9e9..36011a78c 100644 --- a/Examples/UICatalog/Scenarios/ConfigurationEditor.cs +++ b/Examples/UICatalog/Scenarios/ConfigurationEditor.cs @@ -75,7 +75,7 @@ public class ConfigurationEditor : Scenario void ConfigurationManagerOnApplied (object? sender, ConfigurationManagerEventArgs e) { - Application.Top?.SetNeedsDraw (); + Application.Current?.SetNeedsDraw (); } } public void Save () diff --git a/Examples/UICatalog/Scenarios/ContextMenus.cs b/Examples/UICatalog/Scenarios/ContextMenus.cs index 141392f29..bb3a7b19e 100644 --- a/Examples/UICatalog/Scenarios/ContextMenus.cs +++ b/Examples/UICatalog/Scenarios/ContextMenus.cs @@ -1,5 +1,7 @@ -using System.Globalization; +#nullable enable +using System.Globalization; using JetBrains.Annotations; +// ReSharper disable AccessToDisposedClosure namespace UICatalog.Scenarios; @@ -7,70 +9,33 @@ namespace UICatalog.Scenarios; [ScenarioCategory ("Menus")] public class ContextMenus : Scenario { - [CanBeNull] - private PopoverMenu _winContextMenu; - private TextField _tfTopLeft, _tfTopRight, _tfMiddle, _tfBottomLeft, _tfBottomRight; - private readonly List _cultureInfos = Application.SupportedCultures; + private PopoverMenu? _winContextMenu; + private TextField? _tfTopLeft, _tfTopRight, _tfMiddle, _tfBottomLeft, _tfBottomRight; + private readonly List? _cultureInfos = Application.SupportedCultures; private readonly Key _winContextMenuKey = Key.Space.WithCtrl; + private Window? _appWindow; + public override void Main () { // Init Application.Init (); // Setup - Create a top-level application window and configure it. - Window appWindow = new () + _appWindow = new () { Title = GetQuitKeyAndName (), Arrangement = ViewArrangement.Fixed, SchemeName = "Toplevel" }; - var text = "Context Menu"; - var width = 20; - - CreateWinContextMenu (); - - var label = new Label - { - X = Pos.Center (), Y = 1, Text = $"Press '{_winContextMenuKey}' to open the Window context menu." - }; - appWindow.Add (label); - - label = new () - { - X = Pos.Center (), - Y = Pos.Bottom (label), - Text = $"Press '{PopoverMenu.DefaultKey}' to open the TextField context menu." - }; - appWindow.Add (label); - - _tfTopLeft = new () { Id = "_tfTopLeft", Width = width, Text = text }; - appWindow.Add (_tfTopLeft); - - _tfTopRight = new () { Id = "_tfTopRight", X = Pos.AnchorEnd (width), Width = width, Text = text }; - appWindow.Add (_tfTopRight); - - _tfMiddle = new () { Id = "_tfMiddle", X = Pos.Center (), Y = Pos.Center (), Width = width, Text = text }; - appWindow.Add (_tfMiddle); - - _tfBottomLeft = new () { Id = "_tfBottomLeft", Y = Pos.AnchorEnd (1), Width = width, Text = text }; - appWindow.Add (_tfBottomLeft); - - _tfBottomRight = new () { Id = "_tfBottomRight", X = Pos.AnchorEnd (width), Y = Pos.AnchorEnd (1), Width = width, Text = text }; - appWindow.Add (_tfBottomRight); - - appWindow.KeyDown += OnAppWindowOnKeyDown; - appWindow.MouseClick += OnAppWindowOnMouseClick; - - CultureInfo originalCulture = Thread.CurrentThread.CurrentUICulture; - appWindow.Closed += (s, e) => { Thread.CurrentThread.CurrentUICulture = originalCulture; }; + _appWindow.Initialized += AppWindowOnInitialized; // Run - Start the application. - Application.Run (appWindow); - appWindow.Dispose (); - appWindow.KeyDown -= OnAppWindowOnKeyDown; - appWindow.MouseClick -= OnAppWindowOnMouseClick; + Application.Run (_appWindow); + _appWindow.Dispose (); + _appWindow.KeyDown -= OnAppWindowOnKeyDown; + _appWindow.MouseClick -= OnAppWindowOnMouseClick; _winContextMenu?.Dispose (); // Shutdown - Calling Application.Shutdown is required. @@ -78,7 +43,51 @@ public class ContextMenus : Scenario return; - void OnAppWindowOnMouseClick (object s, MouseEventArgs e) + void AppWindowOnInitialized (object? sender, EventArgs e) + { + + var text = "Context Menu"; + var width = 20; + + CreateWinContextMenu (); + + var label = new Label + { + X = Pos.Center (), Y = 1, Text = $"Press '{_winContextMenuKey}' to open the Window context menu." + }; + _appWindow.Add (label); + + label = new () + { + X = Pos.Center (), + Y = Pos.Bottom (label), + Text = $"Press '{PopoverMenu.DefaultKey}' to open the TextField context menu." + }; + _appWindow.Add (label); + + _tfTopLeft = new () { Id = "_tfTopLeft", Width = width, Text = text }; + _appWindow.Add (_tfTopLeft); + + _tfTopRight = new () { Id = "_tfTopRight", X = Pos.AnchorEnd (width), Width = width, Text = text }; + _appWindow.Add (_tfTopRight); + + _tfMiddle = new () { Id = "_tfMiddle", X = Pos.Center (), Y = Pos.Center (), Width = width, Text = text }; + _appWindow.Add (_tfMiddle); + + _tfBottomLeft = new () { Id = "_tfBottomLeft", Y = Pos.AnchorEnd (1), Width = width, Text = text }; + _appWindow.Add (_tfBottomLeft); + + _tfBottomRight = new () { Id = "_tfBottomRight", X = Pos.AnchorEnd (width), Y = Pos.AnchorEnd (1), Width = width, Text = text }; + _appWindow.Add (_tfBottomRight); + + _appWindow.KeyDown += OnAppWindowOnKeyDown; + _appWindow.MouseClick += OnAppWindowOnMouseClick; + + CultureInfo originalCulture = Thread.CurrentThread.CurrentUICulture; + _appWindow.Closed += (s, e) => { Thread.CurrentThread.CurrentUICulture = originalCulture; }; + } + + void OnAppWindowOnMouseClick (object? s, MouseEventArgs e) { if (e.Flags == MouseFlags.Button3Clicked) { @@ -88,7 +97,7 @@ public class ContextMenus : Scenario } } - void OnAppWindowOnKeyDown (object s, Key e) + void OnAppWindowOnKeyDown (object? s, Key e) { if (e == _winContextMenuKey) { @@ -101,12 +110,6 @@ public class ContextMenus : Scenario private void CreateWinContextMenu () { - if (_winContextMenu is { }) - { - _winContextMenu.Dispose (); - _winContextMenu = null; - } - _winContextMenu = new ( [ new MenuItemv2 @@ -171,6 +174,7 @@ public class ContextMenus : Scenario { Key = _winContextMenuKey }; + Application.Popover?.Register (_winContextMenu); } private Menuv2 GetSupportedCultureMenu () @@ -178,7 +182,7 @@ public class ContextMenus : Scenario List supportedCultures = []; int index = -1; - foreach (CultureInfo c in _cultureInfos) + foreach (CultureInfo c in _cultureInfos!) { MenuItemv2 culture = new (); diff --git a/Examples/UICatalog/Scenarios/CsvEditor.cs b/Examples/UICatalog/Scenarios/CsvEditor.cs index 609cb4e06..aca10fdac 100644 --- a/Examples/UICatalog/Scenarios/CsvEditor.cs +++ b/Examples/UICatalog/Scenarios/CsvEditor.cs @@ -502,7 +502,7 @@ public class CsvEditor : Scenario // Only set the current filename if we successfully loaded the entire file _currentFile = filename; _selectedCellTextField.SuperView.Enabled = true; - Application.Top.Title = $"{GetName ()} - {Path.GetFileName (_currentFile)}"; + Application.Current.Title = $"{GetName ()} - {Path.GetFileName (_currentFile)}"; } catch (Exception ex) { diff --git a/Examples/UICatalog/Scenarios/Images.cs b/Examples/UICatalog/Scenarios/Images.cs index 488f595a7..a9854092d 100644 --- a/Examples/UICatalog/Scenarios/Images.cs +++ b/Examples/UICatalog/Scenarios/Images.cs @@ -151,7 +151,7 @@ public class Images : Scenario _win.Add (_tabView); // Start trying to detect sixel support - var sixelSupportDetector = new SixelSupportDetector (); + var sixelSupportDetector = new SixelSupportDetector (Application.Driver); sixelSupportDetector.Detect (UpdateSixelSupportState); Application.Run (_win); diff --git a/Examples/UICatalog/Scenarios/Mazing.cs b/Examples/UICatalog/Scenarios/Mazing.cs index 01bbc41a4..935c72ae5 100644 --- a/Examples/UICatalog/Scenarios/Mazing.cs +++ b/Examples/UICatalog/Scenarios/Mazing.cs @@ -171,7 +171,7 @@ public class Mazing : Scenario if (_m.PlayerHp <= 0) { _message = "You died!"; - Application.Top!.SetNeedsDraw (); // trigger redraw + Application.Current!.SetNeedsDraw (); // trigger redraw _dead = true; return; // Stop further action if dead @@ -190,7 +190,7 @@ public class Mazing : Scenario _message = string.Empty; } - Application.Top!.SetNeedsDraw (); // trigger redraw + Application.Current!.SetNeedsDraw (); // trigger redraw } // Optional win condition: @@ -200,7 +200,7 @@ public class Mazing : Scenario _m = new (); // Generate a new maze _m.PlayerHp = hp; GenerateNpcs (); - Application.Top!.SetNeedsDraw (); // trigger redraw + Application.Current!.SetNeedsDraw (); // trigger redraw } } } diff --git a/Examples/UICatalog/Scenarios/Menus.cs b/Examples/UICatalog/Scenarios/Menus.cs index 70f67f6e2..755844715 100644 --- a/Examples/UICatalog/Scenarios/Menus.cs +++ b/Examples/UICatalog/Scenarios/Menus.cs @@ -121,7 +121,7 @@ public class Menus : Scenario Command.Cancel, ctx => { - if (Application.Popover?.GetActivePopover () as PopoverMenu is { Visible: true } visiblePopover) + if (App?.Popover?.GetActivePopover () as PopoverMenu is { Visible: true } visiblePopover) { visiblePopover.Visible = false; } diff --git a/Examples/UICatalog/Scenarios/Navigation.cs b/Examples/UICatalog/Scenarios/Navigation.cs index 7ce1d3317..94e7dad8e 100644 --- a/Examples/UICatalog/Scenarios/Navigation.cs +++ b/Examples/UICatalog/Scenarios/Navigation.cs @@ -219,7 +219,7 @@ public class Navigation : Scenario progressBar.Fraction += 0.01f; - Application.Invoke (() => { }); + Application.Invoke ((_) => { }); } void ColorPicker_ColorChanged (object sender, ResultEventArgs e) diff --git a/Examples/UICatalog/Scenarios/Notepad.cs b/Examples/UICatalog/Scenarios/Notepad.cs index 996702298..595604a7b 100644 --- a/Examples/UICatalog/Scenarios/Notepad.cs +++ b/Examples/UICatalog/Scenarios/Notepad.cs @@ -294,7 +294,7 @@ public class Notepad : Scenario // Registering with the PopoverManager will ensure that the context menu is closed when the view is no longer focused // and the context menu is disposed when it is closed. - Application.Popover?.Register (contextMenu); + tv.App!.Popover?.Register (contextMenu); contextMenu?.MakeVisible (e.MouseEvent.ScreenPosition); e.MouseEvent.Handled = true; diff --git a/Examples/UICatalog/Scenarios/Progress.cs b/Examples/UICatalog/Scenarios/Progress.cs index 4696c160c..0e3af66c4 100644 --- a/Examples/UICatalog/Scenarios/Progress.cs +++ b/Examples/UICatalog/Scenarios/Progress.cs @@ -43,7 +43,7 @@ public class Progress : Scenario { // Note the check for Mainloop being valid. System.Timers can run after they are Disposed. // This code must be defensive for that. - Application.Invoke (() => systemTimerDemo.Pulse ()); + Application.Invoke ((_) => systemTimerDemo.Pulse ()); }, null, 0, diff --git a/Examples/UICatalog/Scenarios/RegionScenario.cs b/Examples/UICatalog/Scenarios/RegionScenario.cs index dea5d5f0a..495d90d6a 100644 --- a/Examples/UICatalog/Scenarios/RegionScenario.cs +++ b/Examples/UICatalog/Scenarios/RegionScenario.cs @@ -24,31 +24,31 @@ public class RegionScenario : Scenario { Application.Init (); - Window app = new () + Window appWindow = new () { Title = GetQuitKeyAndName (), TabStop = TabBehavior.TabGroup }; - app.Padding!.Thickness = new (1); + appWindow.Padding!.Thickness = new (1); var tools = new ToolsView { Title = "Tools", X = Pos.AnchorEnd (), Y = 2 }; - tools.CurrentAttribute = app.GetAttributeForRole (VisualRole.HotNormal); + tools.CurrentAttribute = appWindow.GetAttributeForRole (VisualRole.HotNormal); tools.SetStyle += b => { _drawStyle = b; - app.SetNeedsDraw (); + appWindow.SetNeedsDraw (); }; tools.RegionOpChanged += (s, e) => { _regionOp = e; }; //tools.AddLayer += () => canvas.AddLayer (); - app.Add (tools); + appWindow.Add (tools); // Add drag handling to window - app.MouseEvent += (s, e) => + appWindow.MouseEvent += (s, e) => { if (e.Flags.HasFlag (MouseFlags.Button1Pressed)) { @@ -62,7 +62,7 @@ public class RegionScenario : Scenario // Drag if (_isDragging && _dragStart.HasValue) { - app.SetNeedsDraw (); + appWindow.SetNeedsDraw (); } } } @@ -77,31 +77,31 @@ public class RegionScenario : Scenario _dragStart = null; } - app.SetNeedsDraw (); + appWindow.SetNeedsDraw (); } }; // Draw the regions - app.DrawingContent += (s, e) => + appWindow.DrawingContent += (s, e) => { // Draw all regions with single line style //_region.FillRectangles (_attribute.Value, _fillRune); switch (_drawStyle) { case RegionDrawStyles.FillOnly: - _region.FillRectangles (tools.CurrentAttribute!.Value, _previewFillRune); + _region.FillRectangles (appWindow.App?.Driver, tools.CurrentAttribute!.Value, _previewFillRune); break; case RegionDrawStyles.InnerBoundaries: - _region.DrawBoundaries (app.LineCanvas, LineStyle.Single, tools.CurrentAttribute); - _region.FillRectangles (tools.CurrentAttribute!.Value, (Rune)' '); + _region.DrawBoundaries (appWindow.LineCanvas, LineStyle.Single, tools.CurrentAttribute); + _region.FillRectangles (appWindow.App?.Driver, tools.CurrentAttribute!.Value, (Rune)' '); break; case RegionDrawStyles.OuterBoundary: - _region.DrawOuterBoundary (app.LineCanvas, LineStyle.Single, tools.CurrentAttribute); - _region.FillRectangles (tools.CurrentAttribute!.Value, (Rune)' '); + _region.DrawOuterBoundary (appWindow.LineCanvas, LineStyle.Single, tools.CurrentAttribute); + _region.FillRectangles (appWindow.App?.Driver, tools.CurrentAttribute!.Value, (Rune)' '); break; } @@ -109,14 +109,14 @@ public class RegionScenario : Scenario // If currently dragging, draw preview rectangle if (_isDragging && _dragStart.HasValue) { - Point currentMousePos = Application.GetLastMousePosition ()!.Value; + Point currentMousePos = appWindow.App!.Mouse.LastMousePosition!.Value; Rectangle previewRect = GetRectFromPoints (_dragStart.Value, currentMousePos); var previewRegion = new Region (previewRect); - previewRegion.FillRectangles (tools.CurrentAttribute!.Value, (Rune)' '); + previewRegion.FillRectangles (appWindow.App.Driver, tools.CurrentAttribute!.Value, (Rune)' '); previewRegion.DrawBoundaries ( - app.LineCanvas, + appWindow.LineCanvas, LineStyle.Dashed, new ( tools.CurrentAttribute!.Value.Foreground.GetBrighterColor (), @@ -124,10 +124,10 @@ public class RegionScenario : Scenario } }; - Application.Run (app); + Application.Run (appWindow); // Clean up - app.Dispose (); + appWindow.Dispose (); Application.Shutdown (); } diff --git a/Examples/UICatalog/Scenarios/Shortcuts.cs b/Examples/UICatalog/Scenarios/Shortcuts.cs index 712b69acf..331ed0337 100644 --- a/Examples/UICatalog/Scenarios/Shortcuts.cs +++ b/Examples/UICatalog/Scenarios/Shortcuts.cs @@ -28,7 +28,7 @@ public class Shortcuts : Scenario private void App_Loaded (object? sender, EventArgs e) { Application.QuitKey = Key.F4.WithCtrl; - Application.Top!.Title = GetQuitKeyAndName (); + Application.Current!.Title = GetQuitKeyAndName (); ObservableCollection eventSource = new (); @@ -46,14 +46,14 @@ public class Shortcuts : Scenario eventLog.Width = Dim.Func ( _ => Math.Min ( - Application.Top.Viewport.Width / 2, + Application.Current.Viewport.Width / 2, eventLog?.MaxLength + eventLog!.GetAdornmentsThickness ().Horizontal ?? 0)); eventLog.Width = Dim.Func ( _ => Math.Min ( eventLog.SuperView!.Viewport.Width / 2, eventLog?.MaxLength + eventLog!.GetAdornmentsThickness ().Horizontal ?? 0)); - Application.Top.Add (eventLog); + Application.Current.Add (eventLog); var alignKeysShortcut = new Shortcut { @@ -86,7 +86,7 @@ public class Shortcuts : Scenario }; - Application.Top.Add (alignKeysShortcut); + Application.Current.Add (alignKeysShortcut); var commandFirstShortcut = new Shortcut { @@ -115,7 +115,7 @@ public class Shortcuts : Scenario $"{commandFirstShortcut.Id}.CommandView.CheckedStateChanging: {cb.Text}"); eventLog.MoveDown (); - IEnumerable toAlign = Application.Top.SubViews.OfType (); + IEnumerable toAlign = Application.Current.SubViews.OfType (); IEnumerable enumerable = toAlign as View [] ?? toAlign.ToArray (); foreach (View view in enumerable) @@ -134,7 +134,7 @@ public class Shortcuts : Scenario } }; - Application.Top.Add (commandFirstShortcut); + Application.Current.Add (commandFirstShortcut); var canFocusShortcut = new Shortcut { @@ -159,7 +159,7 @@ public class Shortcuts : Scenario SetCanFocus (e.Result == CheckState.Checked); } }; - Application.Top.Add (canFocusShortcut); + Application.Current.Add (canFocusShortcut); var appShortcut = new Shortcut { @@ -173,7 +173,7 @@ public class Shortcuts : Scenario BindKeyToApplication = true }; - Application.Top.Add (appShortcut); + Application.Current.Add (appShortcut); var buttonShortcut = new Shortcut { @@ -193,7 +193,7 @@ public class Shortcuts : Scenario var button = (Button)buttonShortcut.CommandView; buttonShortcut.Accepting += Button_Clicked; - Application.Top.Add (buttonShortcut); + Application.Current.Add (buttonShortcut); var optionSelectorShortcut = new Shortcut { @@ -221,7 +221,7 @@ public class Shortcuts : Scenario } }; - Application.Top.Add (optionSelectorShortcut); + Application.Current.Add (optionSelectorShortcut); var sliderShortcut = new Shortcut { @@ -248,7 +248,7 @@ public class Shortcuts : Scenario eventLog.MoveDown (); }; - Application.Top.Add (sliderShortcut); + Application.Current.Add (sliderShortcut); ListView listView = new ListView () { @@ -270,7 +270,7 @@ public class Shortcuts : Scenario Key = Key.F5.WithCtrl, }; - Application.Top.Add (listViewShortcut); + Application.Current.Add (listViewShortcut); var noCommandShortcut = new Shortcut { @@ -282,7 +282,7 @@ public class Shortcuts : Scenario Key = Key.D0 }; - Application.Top.Add (noCommandShortcut); + Application.Current.Add (noCommandShortcut); var noKeyShortcut = new Shortcut { @@ -295,7 +295,7 @@ public class Shortcuts : Scenario HelpText = "Keyless" }; - Application.Top.Add (noKeyShortcut); + Application.Current.Add (noKeyShortcut); var noHelpShortcut = new Shortcut { @@ -308,7 +308,7 @@ public class Shortcuts : Scenario HelpText = "" }; - Application.Top.Add (noHelpShortcut); + Application.Current.Add (noHelpShortcut); noHelpShortcut.SetFocus (); var framedShortcut = new Shortcut @@ -340,7 +340,7 @@ public class Shortcuts : Scenario } framedShortcut.SchemeName = SchemeManager.SchemesToSchemeName (Schemes.Toplevel); - Application.Top.Add (framedShortcut); + Application.Current.Add (framedShortcut); // Horizontal var progressShortcut = new Shortcut @@ -387,7 +387,7 @@ public class Shortcuts : Scenario }; timer.Start (); - Application.Top.Add (progressShortcut); + Application.Current.Add (progressShortcut); var textField = new TextField { @@ -408,7 +408,7 @@ public class Shortcuts : Scenario }; textField.CanFocus = true; - Application.Top.Add (textFieldShortcut); + Application.Current.Add (textFieldShortcut); var bgColorShortcut = new Shortcut { @@ -450,19 +450,19 @@ public class Shortcuts : Scenario eventSource.Add ($"ColorChanged: {o.GetType ().Name} - {args.Result}"); eventLog.MoveDown (); - Application.Top.SetScheme ( - new (Application.Top.GetScheme ()) + Application.Current.SetScheme ( + new (Application.Current.GetScheme ()) { Normal = new ( - Application.Top!.GetAttributeForRole (VisualRole.Normal).Foreground, + Application.Current!.GetAttributeForRole (VisualRole.Normal).Foreground, args.Result, - Application.Top!.GetAttributeForRole (VisualRole.Normal).Style) + Application.Current!.GetAttributeForRole (VisualRole.Normal).Style) }); } }; bgColorShortcut.CommandView = bgColor; - Application.Top.Add (bgColorShortcut); + Application.Current.Add (bgColorShortcut); var appQuitShortcut = new Shortcut { @@ -476,9 +476,9 @@ public class Shortcuts : Scenario }; appQuitShortcut.Accepting += (o, args) => { Application.RequestStop (); }; - Application.Top.Add (appQuitShortcut); + Application.Current.Add (appQuitShortcut); - foreach (Shortcut shortcut in Application.Top.SubViews.OfType ()) + foreach (Shortcut shortcut in Application.Current.SubViews.OfType ()) { shortcut.Selecting += (o, args) => { @@ -529,7 +529,7 @@ public class Shortcuts : Scenario void SetCanFocus (bool canFocus) { - foreach (Shortcut peer in Application.Top!.SubViews.OfType ()) + foreach (Shortcut peer in Application.Current!.SubViews.OfType ()) { if (peer.CanFocus) { @@ -542,7 +542,7 @@ public class Shortcuts : Scenario { var max = 0; - IEnumerable toAlign = Application.Top!.SubViews.OfType ().Where(s => !s.Y.Has(out _)).Cast(); + IEnumerable toAlign = Application.Current!.SubViews.OfType ().Where(s => !s.Y.Has(out _)).Cast(); IEnumerable enumerable = toAlign as Shortcut [] ?? toAlign.ToArray (); if (align) diff --git a/Examples/UICatalog/Scenarios/SingleBackgroundWorker.cs b/Examples/UICatalog/Scenarios/SingleBackgroundWorker.cs index 62c35ac40..571597f8c 100644 --- a/Examples/UICatalog/Scenarios/SingleBackgroundWorker.cs +++ b/Examples/UICatalog/Scenarios/SingleBackgroundWorker.cs @@ -179,9 +179,9 @@ public class SingleBackgroundWorker : Scenario var builderUI = new StagingUIController (_startStaging, e.Result as ObservableCollection); - Toplevel top = Application.Top; + Toplevel top = Application.Current; top.Visible = false; - Application.Top.Visible = false; + Application.Current.Visible = false; builderUI.Load (); builderUI.Dispose (); top.Visible = true; diff --git a/Examples/UICatalog/Scenarios/TableEditor.cs b/Examples/UICatalog/Scenarios/TableEditor.cs index 84d7644b9..57b1f9a60 100644 --- a/Examples/UICatalog/Scenarios/TableEditor.cs +++ b/Examples/UICatalog/Scenarios/TableEditor.cs @@ -1363,7 +1363,7 @@ public class TableEditor : Scenario // Registering with the PopoverManager will ensure that the context menu is closed when the view is no longer focused // and the context menu is disposed when it is closed. - Application.Popover?.Register (contextMenu); + e.View?.App!.Popover?.Register (contextMenu); contextMenu?.MakeVisible (new (e.ScreenPosition.X + 1, e.ScreenPosition.Y + 1)); } diff --git a/Examples/UICatalog/Scenarios/Themes.cs b/Examples/UICatalog/Scenarios/Themes.cs index 6e4d676ea..d515b5ee0 100644 --- a/Examples/UICatalog/Scenarios/Themes.cs +++ b/Examples/UICatalog/Scenarios/Themes.cs @@ -129,7 +129,7 @@ public sealed class Themes : Scenario { if (_view is { }) { - Application.Top!.SchemeName = args.NewValue; + Application.Current!.SchemeName = args.NewValue; if (_view.HasScheme) { @@ -160,11 +160,11 @@ public sealed class Themes : Scenario TabStop = TabBehavior.TabStop }; - allViewsView.FocusedChanged += (s, args) => + allViewsView.FocusedChanged += (s, a) => { allViewsView.Title = - $"All Views - Focused: {args.NewFocused.Title}"; - viewPropertiesEditor.ViewToEdit = args.NewFocused.SubViews.ElementAt(0); + $"All Views - Focused: {a.NewFocused?.Title}"; + viewPropertiesEditor.ViewToEdit = a.NewFocused?.SubViews.ElementAt(0); }; appWindow.Add (allViewsView); diff --git a/Examples/UICatalog/Scenarios/TreeUseCases.cs b/Examples/UICatalog/Scenarios/TreeUseCases.cs index 4603b9e54..d9ee48d15 100644 --- a/Examples/UICatalog/Scenarios/TreeUseCases.cs +++ b/Examples/UICatalog/Scenarios/TreeUseCases.cs @@ -77,7 +77,7 @@ public class TreeUseCases : Scenario if (_currentTree != null) { - Application.Top.Remove (_currentTree); + Application.Current.Remove (_currentTree); _currentTree.Dispose (); } @@ -97,7 +97,7 @@ public class TreeUseCases : Scenario tree.TreeBuilder = new GameObjectTreeBuilder (); } - Application.Top.Add (tree); + Application.Current.Add (tree); tree.AddObject (army1); @@ -117,13 +117,13 @@ public class TreeUseCases : Scenario if (_currentTree != null) { - Application.Top.Remove (_currentTree); + Application.Current.Remove (_currentTree); _currentTree.Dispose (); } var tree = new TreeView { X = 0, Y = 1, Width = Dim.Fill(), Height = Dim.Fill (1) }; - Application.Top.Add (tree); + Application.Current.Add (tree); tree.AddObject (myHouse); @@ -134,13 +134,13 @@ public class TreeUseCases : Scenario { if (_currentTree != null) { - Application.Top.Remove (_currentTree); + Application.Current.Remove (_currentTree); _currentTree.Dispose (); } var tree = new TreeView { X = 0, Y = 1, Width = Dim.Fill (), Height = Dim.Fill (1) }; - Application.Top.Add (tree); + Application.Current.Add (tree); var root1 = new TreeNode ("Root1"); root1.Children.Add (new TreeNode ("Child1.1")); diff --git a/Examples/UICatalog/Scenarios/TreeViewFileSystem.cs b/Examples/UICatalog/Scenarios/TreeViewFileSystem.cs index 34540d5cc..eeb4f8b78 100644 --- a/Examples/UICatalog/Scenarios/TreeViewFileSystem.cs +++ b/Examples/UICatalog/Scenarios/TreeViewFileSystem.cs @@ -418,7 +418,7 @@ public class TreeViewFileSystem : Scenario // Registering with the PopoverManager will ensure that the context menu is closed when the view is no longer focused // and the context menu is disposed when it is closed. - Application.Popover?.Register (contextMenu); + _detailsFrame.App?.Popover?.Register (contextMenu); Application.Invoke (() => contextMenu?.MakeVisible (screenPoint)); } diff --git a/Examples/UICatalog/Scenarios/ViewExperiments.cs b/Examples/UICatalog/Scenarios/ViewExperiments.cs index b7abf8588..c36fce3e7 100644 --- a/Examples/UICatalog/Scenarios/ViewExperiments.cs +++ b/Examples/UICatalog/Scenarios/ViewExperiments.cs @@ -75,15 +75,15 @@ public class ViewExperiments : Scenario Y = Pos.Center (), Title = $"_Close", }; - //popoverButton.Accepting += (sender, e) => Application.Popover!.Visible = false; + //popoverButton.Accepting += (sender, e) => App?.Popover!.Visible = false; popoverView.Add (popoverButton); button.Accepting += ButtonAccepting; void ButtonAccepting (object sender, CommandEventArgs e) { - //Application.Popover = popoverView; - //Application.Popover!.Visible = true; + //App?.Popover = popoverView; + //App?.Popover!.Visible = true; } testFrame.MouseClick += TestFrameOnMouseClick; @@ -94,8 +94,8 @@ public class ViewExperiments : Scenario { popoverView.X = e.ScreenPosition.X; popoverView.Y = e.ScreenPosition.Y; - //Application.Popover = popoverView; - //Application.Popover!.Visible = true; + //App?.Popover = popoverView; + //App?.Popover!.Visible = true; } } diff --git a/Examples/UICatalog/Scenarios/WindowsAndFrameViews.cs b/Examples/UICatalog/Scenarios/WindowsAndFrameViews.cs index fcf57343a..afe039f1c 100644 --- a/Examples/UICatalog/Scenarios/WindowsAndFrameViews.cs +++ b/Examples/UICatalog/Scenarios/WindowsAndFrameViews.cs @@ -69,7 +69,7 @@ public class WindowsAndFrameViews : Scenario // add it to our list listWin.Add (win); - // create 3 more Windows in a loop, adding them Application.Top + // create 3 more Windows in a loop, adding them Application.Current // Each with a // button // sub Window with diff --git a/Examples/UICatalog/UICatalog.cs b/Examples/UICatalog/UICatalog.cs index 6f7a37139..446057259 100644 --- a/Examples/UICatalog/UICatalog.cs +++ b/Examples/UICatalog/UICatalog.cs @@ -242,7 +242,7 @@ public class UICatalog /// /// Shows the UI Catalog selection UI. When the user selects a Scenario to run, the UI Catalog main app UI is - /// killed and the Scenario is run as though it were Application.Top. When the Scenario exits, this function exits. + /// killed and the Scenario is run as though it were Application.Current. When the Scenario exits, this function exits. /// /// private static Scenario RunUICatalogTopLevel () @@ -269,7 +269,7 @@ public class UICatalog [SuppressMessage ("Style", "IDE1006:Naming Styles", Justification = "")] private static readonly FileSystemWatcher _homeDirWatcher = new (); - private static void StartConfigFileWatcher () + private static void StartConfigWatcher () { // Set up a file system watcher for `./.tui/` _currentDirWatcher.NotifyFilter = NotifyFilters.LastWrite; @@ -317,10 +317,19 @@ public class UICatalog //_homeDirWatcher.Created += ConfigFileChanged; _homeDirWatcher.EnableRaisingEvents = true; + + ThemeManager.ThemeChanged += ThemeManagerOnThemeChanged; } - private static void StopConfigFileWatcher () + private static void ThemeManagerOnThemeChanged (object? sender, EventArgs e) { + CM.Apply (); + } + + private static void StopConfigWatcher () + { + ThemeManager.ThemeChanged += ThemeManagerOnThemeChanged; + _currentDirWatcher.EnableRaisingEvents = false; _currentDirWatcher.Changed -= ConfigFileChanged; _currentDirWatcher.Created -= ConfigFileChanged; @@ -332,7 +341,7 @@ public class UICatalog private static void ConfigFileChanged (object sender, FileSystemEventArgs e) { - if (Application.Top == null) + if (Application.Current == null) { return; } @@ -398,7 +407,7 @@ public class UICatalog if (!Options.DontEnableConfigurationManagement) { ConfigurationManager.Enable (ConfigLocations.All); - StartConfigFileWatcher (); + StartConfigWatcher (); } while (RunUICatalogTopLevel () is { } scenario) @@ -440,7 +449,7 @@ public class UICatalog #endif } - StopConfigFileWatcher (); + StopConfigWatcher (); VerifyObjectsWereDisposed (); } diff --git a/Examples/UICatalog/UICatalog.csproj b/Examples/UICatalog/UICatalog.csproj index 0b529c499..88e916f9c 100644 --- a/Examples/UICatalog/UICatalog.csproj +++ b/Examples/UICatalog/UICatalog.csproj @@ -49,4 +49,5 @@ + \ No newline at end of file diff --git a/Examples/UICatalog/UICatalogTop.cs b/Examples/UICatalog/UICatalogTop.cs index afec138a8..88a5e4144 100644 --- a/Examples/UICatalog/UICatalogTop.cs +++ b/Examples/UICatalog/UICatalogTop.cs @@ -211,6 +211,7 @@ public class UICatalogTop : Toplevel return; } ThemeManager.Theme = ThemeManager.GetThemeNames () [(int)args.Value]; + }; var menuItem = new MenuItemv2 @@ -691,7 +692,7 @@ public class UICatalogTop : Toplevel _disableMouseCb!.CheckedState = Application.IsMouseDisabled ? CheckState.Checked : CheckState.UnChecked; _force16ColorsShortcutCb!.CheckedState = Application.Force16Colors ? CheckState.Checked : CheckState.UnChecked; - Application.Top?.SetNeedsDraw (); + Application.Current?.SetNeedsDraw (); } private void ConfigAppliedHandler (object? sender, ConfigurationManagerEventArgs? a) { ConfigApplied (); } diff --git a/NULLABLE_VIEWS_REMAINING.md b/NULLABLE_VIEWS_REMAINING.md new file mode 100644 index 000000000..220e6933f --- /dev/null +++ b/NULLABLE_VIEWS_REMAINING.md @@ -0,0 +1,163 @@ +# View Subclasses Still With `#nullable disable` + +This document lists all View-related files in the `/Views` directory that still have `#nullable disable` set. + +**Total**: 121 files + +## Breakdown by Subdirectory + +### Autocomplete (8 files) +- Autocomplete/AppendAutocomplete.cs +- Autocomplete/AutocompleteBase.cs +- Autocomplete/AutocompleteContext.cs +- Autocomplete/AutocompleteFilepathContext.cs +- Autocomplete/IAutocomplete.cs +- Autocomplete/ISuggestionGenerator.cs +- Autocomplete/SingleWordSuggestionGenerator.cs +- Autocomplete/Suggestion.cs + +### CollectionNavigation (7 files) +- CollectionNavigation/CollectionNavigator.cs +- CollectionNavigation/CollectionNavigatorBase.cs +- CollectionNavigation/DefaultCollectionNavigatorMatcher.cs +- CollectionNavigation/ICollectionNavigator.cs +- CollectionNavigation/ICollectionNavigatorMatcher.cs +- CollectionNavigation/IListCollectionNavigator.cs +- CollectionNavigation/TableCollectionNavigator.cs + +### Color/ColorPicker (13 files) +- Color/BBar.cs +- Color/ColorBar.cs +- Color/ColorModelStrategy.cs +- Color/ColorPicker.16.cs +- Color/ColorPicker.Prompt.cs +- Color/ColorPicker.Style.cs +- Color/ColorPicker.cs +- Color/GBar.cs +- Color/HueBar.cs +- Color/IColorBar.cs +- Color/LightnessBar.cs +- Color/RBar.cs +- Color/SaturationBar.cs +- Color/ValueBar.cs + +### FileDialogs (10 files) +- FileDialogs/AllowedType.cs +- FileDialogs/DefaultFileOperations.cs +- FileDialogs/FileDialogCollectionNavigator.cs +- FileDialogs/FileDialogHistory.cs +- FileDialogs/FileDialogState.cs +- FileDialogs/FileDialogStyle.cs +- FileDialogs/FileDialogTableSource.cs +- FileDialogs/FilesSelectedEventArgs.cs +- FileDialogs/OpenDialog.cs +- FileDialogs/OpenMode.cs +- FileDialogs/SaveDialog.cs + +### GraphView (9 files) +- GraphView/Axis.cs +- GraphView/BarSeriesBar.cs +- GraphView/GraphCellToRender.cs +- GraphView/GraphView.cs +- GraphView/IAnnotation.cs +- GraphView/LegendAnnotation.cs +- GraphView/LineF.cs +- GraphView/PathAnnotation.cs +- GraphView/TextAnnotation.cs + +### Menu (3 files) +- Menu/MenuBarv2.cs +- Menu/Menuv2.cs +- Menu/PopoverMenu.cs + +### Menuv1 (4 files) +- Menuv1/MenuClosingEventArgs.cs +- Menuv1/MenuItemCheckStyle.cs +- Menuv1/MenuOpenedEventArgs.cs +- Menuv1/MenuOpeningEventArgs.cs + +### ScrollBar (2 files) +- ScrollBar/ScrollBar.cs +- ScrollBar/ScrollSlider.cs + +### Selectors (2 files) +- Selectors/FlagSelector.cs +- Selectors/SelectorStyles.cs + +### Slider (9 files) +- Slider/Slider.cs +- Slider/SliderAttributes.cs +- Slider/SliderConfiguration.cs +- Slider/SliderEventArgs.cs +- Slider/SliderOption.cs +- Slider/SliderOptionEventArgs.cs +- Slider/SliderStyle.cs +- Slider/SliderType.cs + +### SpinnerView (2 files) +- SpinnerView/SpinnerStyle.cs +- SpinnerView/SpinnerView.cs + +### TabView (4 files) +- TabView/Tab.cs +- TabView/TabChangedEventArgs.cs +- TabView/TabMouseEventArgs.cs +- TabView/TabStyle.cs + +### TableView (18 files) +- TableView/CellActivatedEventArgs.cs +- TableView/CellColorGetterArgs.cs +- TableView/CellToggledEventArgs.cs +- TableView/CheckBoxTableSourceWrapper.cs +- TableView/CheckBoxTableSourceWrapperByIndex.cs +- TableView/CheckBoxTableSourceWrapperByObject.cs +- TableView/ColumnStyle.cs +- TableView/DataTableSource.cs +- TableView/EnumerableTableSource.cs +- TableView/IEnumerableTableSource.cs +- TableView/ITableSource.cs +- TableView/ListColumnStyle.cs +- TableView/ListTableSource.cs +- TableView/RowColorGetterArgs.cs +- TableView/SelectedCellChangedEventArgs.cs +- TableView/TableSelection.cs +- TableView/TableStyle.cs +- TableView/TableView.cs +- TableView/TreeTableSource.cs + +### TextInput (11 files) +- TextInput/ContentsChangedEventArgs.cs +- TextInput/DateField.cs +- TextInput/HistoryTextItemEventArgs.cs +- TextInput/ITextValidateProvider.cs +- TextInput/NetMaskedTextProvider.cs +- TextInput/TextEditingLineStatus.cs +- TextInput/TextField.cs +- TextInput/TextRegexProvider.cs +- TextInput/TextValidateField.cs +- TextInput/TimeField.cs + +### TreeView (14 files) +- TreeView/AspectGetterDelegate.cs +- TreeView/Branch.cs +- TreeView/DelegateTreeBuilder.cs +- TreeView/DrawTreeViewLineEventArgs.cs +- TreeView/ITreeBuilder.cs +- TreeView/ITreeViewFilter.cs +- TreeView/ObjectActivatedEventArgs.cs +- TreeView/SelectionChangedEventArgs.cs +- TreeView/TreeBuilder.cs +- TreeView/TreeNode.cs +- TreeView/TreeNodeBuilder.cs +- TreeView/TreeStyle.cs +- TreeView/TreeView.cs +- TreeView/TreeViewTextFilter.cs + +### Wizard (3 files) +- Wizard/Wizard.cs +- Wizard/WizardEventArgs.cs +- Wizard/WizardStep.cs + +## Summary + +These 121 View-related files still have `#nullable disable` as they require additional work to be fully nullable-compliant. All other files in the Terminal.Gui library (outside of the Views directory) have been updated to support nullable reference types. diff --git a/PR_DESCRIPTION_UPDATED.md b/PR_DESCRIPTION_UPDATED.md new file mode 100644 index 000000000..8a653ba68 --- /dev/null +++ b/PR_DESCRIPTION_UPDATED.md @@ -0,0 +1,322 @@ +# Fixes #4329 - Major Architectural Improvements: API Rename, Nullable Types, and Application Decoupling + +## Overview + +This PR delivers **three major architectural improvements** to Terminal.Gui v2: + +1. **API Terminology Modernization** - Renamed confusing `Application.Top`/`TopLevels` to intuitive `Application.Current`/`Session Stack` +2. **Nullable Reference Types** - Enabled nullable for 143 non-View library files +3. **Application Decoupling** - Introduced `View.App` property to decouple View hierarchy from static Application class + +**Impact**: 561 files changed, 7,033 insertions(+), 2,736 deletions(-) across library, tests, and examples. + +--- + +## Part 1: API Terminology Modernization (Breaking Change) + +### Changes + +- **`Application.Top` → `Application.Current`** (684 occurrences across codebase) +- **`Application.TopLevels` → `Application.SessionStack`** (31 occurrences) +- Updated `IApplication` interface, `ApplicationImpl`, all tests, examples, and documentation + +### Rationale + +The old naming was ambiguous and inconsistent with .NET patterns: +- `Top` didn't clearly indicate "currently active/running view" +- `TopLevels` exposed implementation detail (it's a stack!) and didn't match `SessionToken` terminology + +New naming follows established patterns: +- `Current` matches `Thread.CurrentThread`, `HttpContext.Current`, `Synchronization Context.Current` +- `SessionStack` clearly describes both content (sessions) and structure (stack), aligning with `SessionToken` + +### Impact Statistics + +| Category | Files Changed | Occurrences Updated | +|----------|---------------|---------------------| +| Terminal.Gui library | 41 | 715 | +| Unit tests | 43 | 631 | +| Integration tests | 3 | 25 | +| Examples | 15 | 15 | +| Documentation | 3 | 14 | +| **Total** | **91** | **~800** | + +###Breaking Changes + +**All references must be updated:** +```csharp +// OLD (v1/early v2) +Application.Top?.SetNeedsDraw(); +foreach (var tl in Application.TopLevels) { } + +// NEW (v2 current) +Application.Current?.SetNeedsDraw(); +foreach (var tl in Application.SessionStack) { } +``` + +--- + +## Part 2: Nullable Reference Types Enabled + +### Changes + +**Phase 1** - Project Configuration (commit 439e161): +- Added `enable` to `Terminal.Gui.csproj` (project-wide default) +- Removed redundant `#nullable enable` from 37 files +- Added `#nullable disable` to 170 files not yet compliant + +**Phase 2** - Non-View Compliance (commit 06bd50d): +- **Removed `#nullable disable` from ALL 143 non-View library files** +- Build successful with 0 errors +- All core infrastructure now fully nullable-aware + +**Phase 3** - Cleanup (commits 97d9c7d, 49d4fb2): +- Fixed duplicate `#nullable` directives in 37 files +- All files now have clean, single nullable directive + +### Impact Statistics + +| Directory | Files Nullable-Enabled | +|-----------|------------------------| +| App/ | 25 ✅ | +| Configuration/ | 24 ✅ | +| ViewBase/ | 30 ✅ | +| Drivers/ | 25 ✅ | +| Drawing/ | 18 ✅ | +| FileServices/ | 7 ✅ | +| Input/ | 6 ✅ | +| Text/ | 5 ✅ | +| Resources/ | 3 ✅ | +| **Views/** | **121 ⏸️ (documented in NULLABLE_VIEWS_REMAINING.md)** | +| **Total Enabled** | **143 files** | + +### Remaining Work + +See [NULLABLE_VIEWS_REMAINING.md](./NULLABLE_VIEWS_REMAINING.md) for the 121 View subclass files still with `#nullable disable`. These require careful migration due to complex view hierarchies and will be addressed in a follow-up PR. + +--- + +## Part 3: Application Decoupling (MASSIVE Change) + +### Problem + +Prior to this PR, Views were tightly coupled to the **static** `Application` class: +- Direct static calls: `Application.Current`, `Application.Driver`, `Application.MainLoop` +- Made Views untestable in isolation +- Violated dependency inversion principle +- Prevented Views from working with different IApplication implementations + +### Solution: `View.App` Property + +Introduced `View.App` property that provides IApplication instance: + +```csharp +// Terminal.Gui/ViewBase/View.cs +public IApplication? App +{ + get => GetApp(); + internal set => _app = value; +} + +private IApplication? GetApp() +{ + // Walk up hierarchy to find IApplication + if (_app is { }) return _app; + if (SuperView is { }) return SuperView.App; + return Application.Instance; // Fallback to global +} +``` + +### Migration Pattern + +**Before** (tightly coupled): +```csharp +// Direct static dependency +Application.Driver.Move(x, y); +if (Application.Current == this) { } +Application.MainLoop.Invoke(() => { }); +``` + +**After** (decoupled via View.App): +```csharp +// Use injected IApplication instance +App?.Driver.Move(x, y); +if (App?.Current == this) { } +App?.MainLoop.Invoke(() => { }); +``` + +### Impact Statistics + +- **90 files changed** in decoupling commit (899fd76) +- **987 insertions, 728 deletions** +- Affects ViewBase, Views, Adornments, Input handling, Drawing + +### Benefits + +✅ **Testability**: Views can now be tested with mock IApplication +✅ **Flexibility**: Views work with any IApplication implementation +✅ **Cleaner Architecture**: Follows dependency injection pattern +✅ **Future-proof**: Enables multi-application scenarios +✅ **Maintainability**: Clearer dependencies, easier to refactor + +### Known Remaining Coupling + +After decoupling work, only **1 direct Application dependency** remains in ViewBase: +- `Border.Arrangement.cs`: Uses `Application.ArrangeKey` for hotkey binding + +Additional investigation areas for future work: +1. Some Views still reference Application for convenience (non-critical) +2. Test infrastructure may have residual static dependencies +3. Example applications use Application.Run (expected pattern) + +--- + +## Part 4: Test Infrastructure Improvements + +### New Test File: `ApplicationImplBeginEndTests.cs` + +Added **16 comprehensive tests** validating fragile Begin/End state management: + +**Critical Test Coverage:** +- `End_ThrowsArgumentException_WhenNotBalanced` - Ensures proper Begin/End pairing +- `End_RestoresCurrentToPreviousToplevel` - Validates Current property management +- `MultipleBeginEnd_MaintainsStackIntegrity` - Tests nested sessions (5 levels deep) + +**Additional Coverage:** +- Argument validation (null checks) +- SessionStack push/pop operations +- Current property state transitions +- Unique ID generation for toplevels +- SessionToken management +- ResetState cleanup behavior +- Toplevel activation/deactivation events + +### Test Quality Improvements + +All new tests follow best practices: +- Work directly with ApplicationImpl instances (no global Application pollution) +- Use try-finally blocks ensuring Shutdown() always called +- Properly dispose toplevels before Shutdown (satisfies DEBUG_IDISPOSABLE assertions) +- No redundant ResetState calls (Shutdown calls it internally) + +**Result**: All 16 new tests + all existing tests passing ✅ + +--- + +## Additional Changes + +### Merged from v2_develop + +- RunState → SessionToken terminology (precedent for this rename) +- Application.TopLevels visibility changed to public (made this rename more important) +- Legacy MainLoop infrastructure removed +- Driver architecture modernization +- Test infrastructure improvements + +### Documentation + +- Created 5 comprehensive terminology proposal documents in `docfx/docs/`: + - `terminology-index.md` - Navigation guide + - `terminology-proposal.md` - Complete analysis + - `terminology-proposal-summary.md` - Quick reference + - `terminology-diagrams.md` - 11 Mermaid diagrams + - `terminology-before-after.md` - Side-by-side examples +- Updated `navigation.md`, `config.md`, `migratingfromv1.md` +- Created `NULLABLE_VIEWS_REMAINING.md` - Tracks remaining nullable work + +--- + +## Testing + +- ✅ **Build**: Successful with 0 errors +- ✅ **Unit Tests**: All 16 new tests + all existing tests passing +- ✅ **Integration Tests**: Updated and passing +- ✅ **Examples**: UICatalog, ReactiveExample, CommunityToolkitExample all updated and functional +- ✅ **Documentation**: Builds successfully + +--- + +## Breaking Changes Summary + +### API Changes (Requires Code Updates) + +1. **`Application.Top` → `Application.Current`** + - All usages must be updated + - Affects any code accessing the currently running toplevel + +2. **`Application.TopLevels` → `Application.SessionStack`** + - All usages must be updated + - Affects code iterating over running sessions + +### Non-Breaking Changes + +- Nullable reference types: Improved type safety, no runtime changes +- View.App property: Additive, existing Application. * calls still work (for now) + +--- + +## Migration Guide + +### For Terminology Changes + +```bash +# Find and replace in your codebase +Application.Top → Application.Current +Application.TopLevels → Application.SessionStack +``` + +### For View.App Usage (Recommended, Not Required) + +When writing new View code or refactoring existing Views: + +```csharp +// Prefer (future-proof, testable) +App?.Driver.AddRune(rune); +if (App?.Current == this) { } + +// Over (works but tightly coupled) +Application.Driver.AddRune(rune); +if (Application.Current == this) { } +``` + +--- + +## Future Work + +### Nullable Types +- Enable nullable for remaining 121 View files +- Document nullable patterns for View subclass authors + +### Application Decoupling +- Remove last `Application.ArrangeKey` reference from Border +- Consider making View.App property public for advanced scenarios +- Add documentation on using View.App for testable Views + +### Tests +- Expand ApplicationImpl test coverage based on new patterns discovered +- Add tests for View.App hierarchy traversal + +--- + +## Pull Request Checklist + +- [x] I've named my PR in the form of "Fixes #issue. Terse description." +- [x] My code follows the style guidelines of Terminal.Gui +- [x] My code follows the Terminal.Gui library design guidelines +- [x] I ran `dotnet test` before commit +- [x] I have made corresponding changes to the API documentation +- [x] My changes generate no new warnings +- [x] I have checked my code and corrected any poor grammar or misspellings +- [x] I conducted basic QA to assure all features are working + +--- + +## Related Issues + +- Fixes #4329 - Rename/Clarify Application.Toplevels/Top Terminology +- Related to #2491 - Toplevel refactoring +- Fixes #4333 (duplicate/related issue) + +--- + +**Note**: This is a large, multi-faceted PR that delivers significant architectural improvements. The changes are well-tested and maintain backward compatibility except for the intentional breaking API rename. The work positions Terminal.Gui v2 for better testability, maintainability, and future enhancements. diff --git a/Terminal.Gui/App/Application.Current.cs b/Terminal.Gui/App/Application.Current.cs new file mode 100644 index 000000000..e94d074e7 --- /dev/null +++ b/Terminal.Gui/App/Application.Current.cs @@ -0,0 +1,18 @@ +using System.Collections.Concurrent; + +namespace Terminal.Gui.App; + +public static partial class Application // Current handling +{ + /// + [Obsolete ("The legacy static Application object is going away.")] public static ConcurrentStack SessionStack => ApplicationImpl.Instance.SessionStack; + + /// The that is currently active. + /// The current toplevel. + [Obsolete ("The legacy static Application object is going away.")] + public static Toplevel? Current + { + get => ApplicationImpl.Instance.Current; + internal set => ApplicationImpl.Instance.Current = value; + } +} diff --git a/Terminal.Gui/App/Application.Driver.cs b/Terminal.Gui/App/Application.Driver.cs index c08fb879f..741134d39 100644 --- a/Terminal.Gui/App/Application.Driver.cs +++ b/Terminal.Gui/App/Application.Driver.cs @@ -1,4 +1,3 @@ -#nullable enable using System.Diagnostics.CodeAnalysis; @@ -7,6 +6,7 @@ namespace Terminal.Gui.App; public static partial class Application // Driver abstractions { /// + [Obsolete ("The legacy static Application object is going away.")] public static IDriver? Driver { get => ApplicationImpl.Instance.Driver; @@ -15,6 +15,7 @@ public static partial class Application // Driver abstractions /// [ConfigurationProperty (Scope = typeof (SettingsScope))] + [Obsolete ("The legacy static Application object is going away.")] public static bool Force16Colors { get => ApplicationImpl.Instance.Force16Colors; @@ -23,6 +24,7 @@ public static partial class Application // Driver abstractions /// [ConfigurationProperty (Scope = typeof (SettingsScope))] + [Obsolete ("The legacy static Application object is going away.")] public static string ForceDriver { get => ApplicationImpl.Instance.ForceDriver; @@ -30,11 +32,13 @@ public static partial class Application // Driver abstractions } /// + [Obsolete ("The legacy static Application object is going away.")] public static List Sixel => ApplicationImpl.Instance.Sixel; /// Gets a list of types and type names that are available. /// [RequiresUnreferencedCode ("AOT")] + [Obsolete ("The legacy static Application object is going away.")] public static (List, List) GetDriverTypes () { // use reflection to get the list of drivers @@ -59,4 +63,4 @@ public static partial class Application // Driver abstractions return (driverTypes, driverTypeNames); } -} \ No newline at end of file +} diff --git a/Terminal.Gui/App/Application.Keyboard.cs b/Terminal.Gui/App/Application.Keyboard.cs index 1fa176a7d..9dbdde854 100644 --- a/Terminal.Gui/App/Application.Keyboard.cs +++ b/Terminal.Gui/App/Application.Keyboard.cs @@ -1,10 +1,9 @@ -#nullable enable - -namespace Terminal.Gui.App; +namespace Terminal.Gui.App; public static partial class Application // Keyboard handling { /// + [Obsolete ("The legacy static Application object is going away.")] public static IKeyboard Keyboard { get => ApplicationImpl.Instance.Keyboard; @@ -13,14 +12,9 @@ public static partial class Application // Keyboard handling } /// + [Obsolete ("The legacy static Application object is going away.")] public static bool RaiseKeyDownEvent (Key key) => ApplicationImpl.Instance.Keyboard.RaiseKeyDownEvent (key); - /// - public static bool? InvokeCommandsBoundToKey (Key key) => ApplicationImpl.Instance.Keyboard.InvokeCommandsBoundToKey (key); - - /// - public static bool? InvokeCommand (Command command, Key key, KeyBinding binding) => ApplicationImpl.Instance.Keyboard.InvokeCommand (command, key, binding); - /// /// Raised when the user presses a key. /// @@ -33,15 +27,14 @@ public static partial class Application // Keyboard handling /// and events. /// Fired after and before . /// + [Obsolete ("The legacy static Application object is going away.")] public static event EventHandler? KeyDown { add => ApplicationImpl.Instance.Keyboard.KeyDown += value; remove => ApplicationImpl.Instance.Keyboard.KeyDown -= value; } - /// - public static bool RaiseKeyUpEvent (Key key) => ApplicationImpl.Instance.Keyboard.RaiseKeyUpEvent (key); - /// + [Obsolete ("The legacy static Application object is going away.")] public static KeyBindings KeyBindings => ApplicationImpl.Instance.Keyboard.KeyBindings; } diff --git a/Terminal.Gui/App/Application.Lifecycle.cs b/Terminal.Gui/App/Application.Lifecycle.cs index 051d6b5c8..802ba7a53 100644 --- a/Terminal.Gui/App/Application.Lifecycle.cs +++ b/Terminal.Gui/App/Application.Lifecycle.cs @@ -1,4 +1,3 @@ -#nullable enable using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Reflection; @@ -11,68 +10,41 @@ namespace Terminal.Gui.App; public static partial class Application // Lifecycle (Init/Shutdown) { + /// + /// Creates a new instance. + /// + /// + /// The recommended pattern is for developers to call Application.Create() and then use the returned + /// instance for all subsequent application operations. + /// + /// A new instance. + public static IApplication Create () { return new ApplicationImpl (); } - /// Initializes a new instance of a Terminal.Gui Application. must be called when the application is closing. - /// Call this method once per instance (or after has been called). - /// - /// This function loads the right for the platform, Creates a . and - /// assigns it to - /// - /// - /// must be called when the application is closing (typically after - /// has returned) to ensure resources are cleaned up and - /// terminal settings - /// restored. - /// - /// - /// The function combines - /// and - /// into a single - /// call. An application can use without explicitly calling - /// . - /// - /// - /// The to use. If neither or - /// are specified the default driver for the platform will be used. - /// - /// - /// The short name (e.g. "dotnet", "windows", "unix", or "fake") of the - /// to use. If neither or are - /// specified the default driver for the platform will be used. - /// + /// [RequiresUnreferencedCode ("AOT")] [RequiresDynamicCode ("AOT")] - public static void Init (IDriver? driver = null, string? driverName = null) + [Obsolete ("The legacy static Application object is going away.")] + public static void Init (string? driverName = null) { - ApplicationImpl.Instance.Init (driver, driverName ?? ForceDriver); + ApplicationImpl.Instance.Init (driverName ?? ForceDriver); } /// /// Gets or sets the main thread ID for the application. /// + [Obsolete ("The legacy static Application object is going away.")] public static int? MainThreadId { get => ((ApplicationImpl)ApplicationImpl.Instance).MainThreadId; set => ((ApplicationImpl)ApplicationImpl.Instance).MainThreadId = value; } - /// Shutdown an application initialized with . - /// - /// Shutdown must be called for every call to or - /// to ensure all resources are cleaned - /// up (Disposed) - /// and terminal settings are restored. - /// + /// + [Obsolete ("The legacy static Application object is going away.")] public static void Shutdown () => ApplicationImpl.Instance.Shutdown (); - /// - /// Gets whether the application has been initialized with and not yet shutdown with . - /// - /// - /// - /// The event is raised after the and methods have been called. - /// - /// + /// + [Obsolete ("The legacy static Application object is going away.")] public static bool Initialized { get => ApplicationImpl.Instance.Initialized; @@ -80,6 +52,7 @@ public static partial class Application // Lifecycle (Init/Shutdown) } /// + [Obsolete ("The legacy static Application object is going away.")] public static event EventHandler>? InitializedChanged { add => ApplicationImpl.Instance.InitializedChanged += value; @@ -91,5 +64,6 @@ public static partial class Application // Lifecycle (Init/Shutdown) // this in a function like this ensures we don't make mistakes in // guaranteeing that the state of this singleton is deterministic when Init // starts running and after Shutdown returns. + [Obsolete ("The legacy static Application object is going away.")] internal static void ResetState (bool ignoreDisposed = false) => ApplicationImpl.Instance?.ResetState (ignoreDisposed); } diff --git a/Terminal.Gui/App/Application.Mouse.cs b/Terminal.Gui/App/Application.Mouse.cs index 659e07b1c..5e6ec118f 100644 --- a/Terminal.Gui/App/Application.Mouse.cs +++ b/Terminal.Gui/App/Application.Mouse.cs @@ -1,17 +1,12 @@ -#nullable enable using System.ComponentModel; namespace Terminal.Gui.App; public static partial class Application // Mouse handling { - /// - /// Gets the most recent position of the mouse. - /// - public static Point? GetLastMousePosition () { return Mouse.GetLastMousePosition (); } - /// Disable or enable the mouse. The mouse is enabled by default. [ConfigurationProperty (Scope = typeof (SettingsScope))] + [Obsolete ("The legacy static Application object is going away.")] public static bool IsMouseDisabled { get => Mouse.IsMouseDisabled; @@ -26,12 +21,8 @@ public static partial class Application // Mouse handling /// This property provides access to mouse-related functionality in a way that supports /// parallel test execution by avoiding static state. /// - /// - /// New code should use Application.Mouse instead of the static properties and methods - /// for better testability. Legacy static properties like and - /// are retained for backward compatibility. - /// /// + [Obsolete ("The legacy static Application object is going away.")] public static IMouse Mouse => ApplicationImpl.Instance.Mouse; #pragma warning disable CS1574 // XML comment has cref attribute that could not be resolved @@ -54,6 +45,7 @@ public static partial class Application // Mouse handling /// Use this even to handle mouse events at the application level, before View-specific handling. /// /// + [Obsolete ("The legacy static Application object is going away.")] public static event EventHandler? MouseEvent { add => Mouse.MouseEvent += value; @@ -65,11 +57,13 @@ public static partial class Application // Mouse handling /// INTERNAL: Holds the non- views that are currently under the /// mouse. /// + [Obsolete ("The legacy static Application object is going away.")] internal static List CachedViewsUnderMouse => Mouse.CachedViewsUnderMouse; /// /// INTERNAL API: Holds the last mouse position. /// + [Obsolete ("The legacy static Application object is going away.")] internal static Point? LastMousePosition { get => Mouse.LastMousePosition; @@ -81,6 +75,7 @@ public static partial class Application // Mouse handling /// /// The position of the mouse. /// The most recent result from GetViewsUnderLocation(). + [Obsolete ("The legacy static Application object is going away.")] internal static void RaiseMouseEnterLeaveEvents (Point screenPosition, List currentViewsUnderMouse) { Mouse.RaiseMouseEnterLeaveEvents (screenPosition, currentViewsUnderMouse); @@ -92,6 +87,7 @@ public static partial class Application // Mouse handling /// /// This method can be used to simulate a mouse event, e.g. in unit tests. /// The mouse event with coordinates relative to the screen. + [Obsolete ("The legacy static Application object is going away.")] internal static void RaiseMouseEvent (MouseEventArgs mouseEvent) { Mouse.RaiseMouseEvent (mouseEvent); diff --git a/Terminal.Gui/App/Application.Navigation.cs b/Terminal.Gui/App/Application.Navigation.cs index b822a6027..b1053ae42 100644 --- a/Terminal.Gui/App/Application.Navigation.cs +++ b/Terminal.Gui/App/Application.Navigation.cs @@ -1,4 +1,3 @@ -#nullable enable namespace Terminal.Gui.App; @@ -7,6 +6,7 @@ public static partial class Application // Navigation stuff /// /// Gets the instance for the current . /// + [Obsolete ("The legacy static Application object is going away.")] public static ApplicationNavigation? Navigation { get => ApplicationImpl.Instance.Navigation; @@ -15,7 +15,7 @@ public static partial class Application // Navigation stuff /// Alternative key to navigate forwards through views. Ctrl+Tab is the primary key. [ConfigurationProperty (Scope = typeof (SettingsScope))] - public static Key NextTabGroupKey + [Obsolete ("The legacy static Application object is going away.")]public static Key NextTabGroupKey { get => ApplicationImpl.Instance.Keyboard.NextTabGroupKey; set => ApplicationImpl.Instance.Keyboard.NextTabGroupKey = value; @@ -41,6 +41,7 @@ public static partial class Application // Navigation stuff /// and events. /// Fired after . /// + [Obsolete ("The legacy static Application object is going away.")] public static event EventHandler? KeyUp { add => ApplicationImpl.Instance.Keyboard.KeyUp += value; diff --git a/Terminal.Gui/App/Application.Popover.cs b/Terminal.Gui/App/Application.Popover.cs index 31522f80f..40eba8d4d 100644 --- a/Terminal.Gui/App/Application.Popover.cs +++ b/Terminal.Gui/App/Application.Popover.cs @@ -1,13 +1,13 @@ -#nullable enable namespace Terminal.Gui.App; public static partial class Application // Popover handling { /// Gets the Application manager. + [Obsolete ("The legacy static Application object is going away.")] public static ApplicationPopover? Popover { get => ApplicationImpl.Instance.Popover; internal set => ApplicationImpl.Instance.Popover = value; } -} \ No newline at end of file +} diff --git a/Terminal.Gui/App/Application.Run.cs b/Terminal.Gui/App/Application.Run.cs index 56206a997..d218c370d 100644 --- a/Terminal.Gui/App/Application.Run.cs +++ b/Terminal.Gui/App/Application.Run.cs @@ -1,4 +1,3 @@ -#nullable enable using System.Diagnostics.CodeAnalysis; namespace Terminal.Gui.App; @@ -22,41 +21,57 @@ public static partial class Application // Run (Begin -> Run -> Layout/Draw -> E } /// + [Obsolete ("The legacy static Application object is going away.")] public static SessionToken Begin (Toplevel toplevel) => ApplicationImpl.Instance.Begin (toplevel); /// + [Obsolete ("The legacy static Application object is going away.")] public static bool PositionCursor () => ApplicationImpl.Instance.PositionCursor (); /// [RequiresUnreferencedCode ("AOT")] [RequiresDynamicCode ("AOT")] - public static Toplevel Run (Func? errorHandler = null, string? driver = null) => ApplicationImpl.Instance.Run (errorHandler, driver); + [Obsolete ("The legacy static Application object is going away.")] + public static Toplevel Run (Func? errorHandler = null, string? driverName = null) => ApplicationImpl.Instance.Run (errorHandler, driverName); /// [RequiresUnreferencedCode ("AOT")] [RequiresDynamicCode ("AOT")] - public static TView Run (Func? errorHandler = null, string? driver = null) - where TView : Toplevel, new() => ApplicationImpl.Instance.Run (errorHandler, driver); + [Obsolete ("The legacy static Application object is going away.")] + public static TView Run (Func? errorHandler = null, string? driverName = null) + where TView : Toplevel, new() => ApplicationImpl.Instance.Run (errorHandler, driverName); /// + [Obsolete ("The legacy static Application object is going away.")] public static void Run (Toplevel view, Func? errorHandler = null) => ApplicationImpl.Instance.Run (view, errorHandler); /// + [Obsolete ("The legacy static Application object is going away.")] public static object? AddTimeout (TimeSpan time, Func callback) => ApplicationImpl.Instance.AddTimeout (time, callback); /// + [Obsolete ("The legacy static Application object is going away.")] public static bool RemoveTimeout (object token) => ApplicationImpl.Instance.RemoveTimeout (token); /// /// + [Obsolete ("The legacy static Application object is going away.")] public static ITimedEvents? TimedEvents => ApplicationImpl.Instance?.TimedEvents; - /// + + /// + [Obsolete ("The legacy static Application object is going away.")] + public static void Invoke (Action action) => ApplicationImpl.Instance.Invoke (action); + + /// + [Obsolete ("The legacy static Application object is going away.")] public static void Invoke (Action action) => ApplicationImpl.Instance.Invoke (action); /// + [Obsolete ("The legacy static Application object is going away.")] public static void LayoutAndDraw (bool forceRedraw = false) => ApplicationImpl.Instance.LayoutAndDraw (forceRedraw); /// + [Obsolete ("The legacy static Application object is going away.")] public static bool StopAfterFirstIteration { get => ApplicationImpl.Instance.StopAfterFirstIteration; @@ -64,15 +79,15 @@ public static partial class Application // Run (Begin -> Run -> Layout/Draw -> E } /// + [Obsolete ("The legacy static Application object is going away.")] public static void RequestStop (Toplevel? top = null) => ApplicationImpl.Instance.RequestStop (top); /// + [Obsolete ("The legacy static Application object is going away.")] public static void End (SessionToken sessionToken) => ApplicationImpl.Instance.End (sessionToken); - /// - internal static void RaiseIteration () => ApplicationImpl.Instance.RaiseIteration (); - /// + [Obsolete ("The legacy static Application object is going away.")] public static event EventHandler? Iteration { add => ApplicationImpl.Instance.Iteration += value; @@ -80,6 +95,7 @@ public static partial class Application // Run (Begin -> Run -> Layout/Draw -> E } /// + [Obsolete ("The legacy static Application object is going away.")] public static event EventHandler? SessionBegun { add => ApplicationImpl.Instance.SessionBegun += value; @@ -87,6 +103,7 @@ public static partial class Application // Run (Begin -> Run -> Layout/Draw -> E } /// + [Obsolete ("The legacy static Application object is going away.")] public static event EventHandler? SessionEnded { add => ApplicationImpl.Instance.SessionEnded += value; diff --git a/Terminal.Gui/App/Application.Screen.cs b/Terminal.Gui/App/Application.Screen.cs index 2cc223cdb..195c33ea6 100644 --- a/Terminal.Gui/App/Application.Screen.cs +++ b/Terminal.Gui/App/Application.Screen.cs @@ -1,4 +1,3 @@ -#nullable enable namespace Terminal.Gui.App; @@ -6,6 +5,7 @@ public static partial class Application // Screen related stuff; intended to hid { /// + [Obsolete ("The legacy static Application object is going away.")] public static Rectangle Screen { get => ApplicationImpl.Instance.Screen; @@ -13,6 +13,7 @@ public static partial class Application // Screen related stuff; intended to hid } /// + [Obsolete ("The legacy static Application object is going away.")] public static event EventHandler>? ScreenChanged { add => ApplicationImpl.Instance.ScreenChanged += value; @@ -21,6 +22,7 @@ public static partial class Application // Screen related stuff; intended to hid /// + [Obsolete ("The legacy static Application object is going away.")] internal static bool ClearScreenNextIteration { get => ApplicationImpl.Instance.ClearScreenNextIteration; diff --git a/Terminal.Gui/App/Application.Toplevel.cs b/Terminal.Gui/App/Application.Toplevel.cs deleted file mode 100644 index e7c05e437..000000000 --- a/Terminal.Gui/App/Application.Toplevel.cs +++ /dev/null @@ -1,18 +0,0 @@ -#nullable enable -using System.Collections.Concurrent; - -namespace Terminal.Gui.App; - -public static partial class Application // Toplevel handling -{ - /// - public static ConcurrentStack TopLevels => ApplicationImpl.Instance.TopLevels; - - /// The that is currently active. - /// The top. - public static Toplevel? Top - { - get => ApplicationImpl.Instance.Top; - internal set => ApplicationImpl.Instance.Top = value; - } -} diff --git a/Terminal.Gui/App/Application.cs b/Terminal.Gui/App/Application.cs index bd3ccf7a3..7213e16be 100644 --- a/Terminal.Gui/App/Application.cs +++ b/Terminal.Gui/App/Application.cs @@ -1,8 +1,7 @@ -#nullable enable - // We use global using directives to simplify the code and avoid repetitive namespace declarations. // Put them here so they are available throughout the application. // Do not put them in AssemblyInfo.cs as it will break GitVersion's /updateassemblyinfo + global using Attribute = Terminal.Gui.Drawing.Attribute; global using Color = Terminal.Gui.Drawing.Color; global using CM = Terminal.Gui.Configuration.ConfigurationManager; @@ -16,7 +15,6 @@ global using Terminal.Gui.Drawing; global using Terminal.Gui.Text; global using Terminal.Gui.Resources; global using Terminal.Gui.FileServices; -using System.Diagnostics; using System.Globalization; using System.Reflection; using System.Resources; @@ -40,95 +38,31 @@ namespace Terminal.Gui.App; public static partial class Application { /// - /// Maximum number of iterations of the main loop (and hence draws) - /// to allow to occur per second. Defaults to > which is a 40ms sleep - /// after iteration (factoring in how long iteration took to run). - /// Note that not every iteration draws (see ). - /// Only affects v2 drivers. + /// Maximum number of iterations of the main loop (and hence draws) + /// to allow to occur per second. Defaults to > which is a 40ms sleep + /// after iteration (factoring in how long iteration took to run). + /// + /// Note that not every iteration draws (see ). + /// Only affects v2 drivers. + /// /// public static ushort MaximumIterationsPerSecond = DefaultMaximumIterationsPerSecond; /// - /// Default value for + /// Default value for /// public const ushort DefaultMaximumIterationsPerSecond = 25; - /// - /// Gets a string representation of the Application as rendered by . - /// - /// A string representation of the Application - public new static string ToString () - { - IDriver? driver = Driver; - - if (driver is null) - { - return string.Empty; - } - - return ToString (driver); - } - - /// - /// Gets a string representation of the Application rendered by the provided . - /// - /// The driver to use to render the contents. - /// A string representation of the Application - public static string ToString (IDriver? driver) - { - if (driver is null) - { - return string.Empty; - } - - var sb = new StringBuilder (); - - Cell [,] contents = driver?.Contents!; - - for (var r = 0; r < driver!.Rows; r++) - { - for (var c = 0; c < driver.Cols; c++) - { - Rune rune = contents [r, c].Rune; - - if (rune.DecodeSurrogatePair (out char []? sp)) - { - sb.Append (sp); - } - else - { - sb.Append ((char)rune.Value); - } - - if (rune.GetColumns () > 1) - { - c++; - } - - // See Issue #2616 - //foreach (var combMark in contents [r, c].CombiningMarks) { - // sb.Append ((char)combMark.Value); - //} - } - - sb.AppendLine (); - } - - return sb.ToString (); - } - /// Gets all cultures supported by the application without the invariant language. public static List? SupportedCultures { get; private set; } = GetSupportedCultures (); - internal static List GetAvailableCulturesFromEmbeddedResources () { ResourceManager rm = new (typeof (Strings)); CultureInfo [] cultures = CultureInfo.GetCultures (CultureTypes.AllCultures); - return cultures.Where ( - cultureInfo => + return cultures.Where (cultureInfo => !cultureInfo.Equals (CultureInfo.InvariantCulture) && rm.GetResourceSet (cultureInfo, true, false) is { } ) @@ -152,8 +86,7 @@ public static partial class Application if (cultures.Length > 1 && Directory.Exists (Path.Combine (assemblyLocation, "pt-PT"))) { // Return all culture for which satellite folder found with culture code. - return cultures.Where ( - cultureInfo => + return cultures.Where (cultureInfo => Directory.Exists (Path.Combine (assemblyLocation, cultureInfo.Name)) && File.Exists (Path.Combine (assemblyLocation, cultureInfo.Name, resourceFilename)) ) diff --git a/Terminal.Gui/App/ApplicationImpl.Driver.cs b/Terminal.Gui/App/ApplicationImpl.Driver.cs index 36679b2b0..837024b83 100644 --- a/Terminal.Gui/App/ApplicationImpl.Driver.cs +++ b/Terminal.Gui/App/ApplicationImpl.Driver.cs @@ -1,4 +1,3 @@ -#nullable enable using System.Collections.Concurrent; namespace Terminal.Gui.App; @@ -80,7 +79,7 @@ public partial class ApplicationImpl Logging.Trace ($"Created Subcomponents: {Coordinator}"); - Coordinator.StartInputTaskAsync ().Wait (); + Coordinator.StartInputTaskAsync (this).Wait (); if (Driver == null) { diff --git a/Terminal.Gui/App/ApplicationImpl.Lifecycle.cs b/Terminal.Gui/App/ApplicationImpl.Lifecycle.cs index 0a7795833..73465bf8a 100644 --- a/Terminal.Gui/App/ApplicationImpl.Lifecycle.cs +++ b/Terminal.Gui/App/ApplicationImpl.Lifecycle.cs @@ -1,4 +1,3 @@ -#nullable enable using System.Diagnostics; using System.Diagnostics.CodeAnalysis; @@ -15,7 +14,7 @@ public partial class ApplicationImpl /// [RequiresUnreferencedCode ("AOT")] [RequiresDynamicCode ("AOT")] - public void Init (IDriver? driver = null, string? driverName = null) + public void Init (string? driverName = null) { if (Initialized) { @@ -34,11 +33,11 @@ public partial class ApplicationImpl _driverName = ForceDriver; } - Debug.Assert (Navigation is null); - Navigation = new (); + // Debug.Assert (Navigation is null); + // Navigation = new (); - Debug.Assert (Popover is null); - Popover = new (); + //Debug.Assert (Popover is null); + //Popover = new (); // Preserve existing keyboard settings if they exist bool hasExistingKeyboard = _keyboard is { }; @@ -50,7 +49,7 @@ public partial class ApplicationImpl Key existingPrevTabGroupKey = _keyboard?.PrevTabGroupKey ?? Key.F6.WithShift; // Reset keyboard to ensure fresh state with default bindings - _keyboard = new KeyboardImpl { Application = this }; + _keyboard = new KeyboardImpl { App = this }; // Restore previously set keys if they existed and were different from defaults if (hasExistingKeyboard) @@ -114,9 +113,6 @@ public partial class ApplicationImpl // Clear the event to prevent memory leaks InitializedChanged = null; - - // Create a new lazy instance for potential future Init - _lazyInstance = new (() => new ApplicationImpl ()); } #if DEBUG @@ -156,8 +152,11 @@ public partial class ApplicationImpl // Init created. Apps that do any threading will need to code defensively for this. // e.g. see Issue #537 + // === 0. Stop all timers === + TimedEvents?.StopAll (); + // === 1. Stop all running toplevels === - foreach (Toplevel? t in TopLevels) + foreach (Toplevel? t in SessionStack) { t!.Running = false; } @@ -170,29 +169,30 @@ public partial class ApplicationImpl popover.Visible = false; } + // Any popovers added to Popover have their lifetime controlled by Popover Popover?.Dispose (); Popover = null; // === 3. Clean up toplevels === - TopLevels.Clear (); + SessionStack.Clear (); #if DEBUG_IDISPOSABLE - // Don't dispose the Top. It's up to caller dispose it - if (View.EnableDebugIDisposableAsserts && !ignoreDisposed && Top is { }) + // Don't dispose the Current. It's up to caller dispose it + if (View.EnableDebugIDisposableAsserts && !ignoreDisposed && Current is { }) { - Debug.Assert (Top.WasDisposed, $"Title = {Top.Title}, Id = {Top.Id}"); + Debug.Assert (Current.WasDisposed, $"Title = {Current.Title}, Id = {Current.Id}"); // If End wasn't called _CachedSessionTokenToplevel may be null if (CachedSessionTokenToplevel is { }) { Debug.Assert (CachedSessionTokenToplevel.WasDisposed); - Debug.Assert (CachedSessionTokenToplevel == Top); + Debug.Assert (CachedSessionTokenToplevel == Current); } } #endif - Top = null; + Current = null; CachedSessionTokenToplevel = null; // === 4. Clean up driver === @@ -222,7 +222,7 @@ public partial class ApplicationImpl // === 7. Clear navigation and screen state === ScreenChanged = null; - Navigation = null; + //Navigation = null; // === 8. Reset initialization state === Initialized = false; diff --git a/Terminal.Gui/App/ApplicationImpl.Run.cs b/Terminal.Gui/App/ApplicationImpl.Run.cs index fd0564e05..4e9f59645 100644 --- a/Terminal.Gui/App/ApplicationImpl.Run.cs +++ b/Terminal.Gui/App/ApplicationImpl.Run.cs @@ -1,4 +1,3 @@ -#nullable enable using System.Diagnostics; using System.Diagnostics.CodeAnalysis; @@ -37,28 +36,28 @@ public partial class ApplicationImpl var rs = new SessionToken (toplevel); #if DEBUG_IDISPOSABLE - if (View.EnableDebugIDisposableAsserts && Top is { } && toplevel != Top && !TopLevels.Contains (Top)) + if (View.EnableDebugIDisposableAsserts && Current is { } && toplevel != Current && !SessionStack.Contains (Current)) { - // This assertion confirm if the Top was already disposed - Debug.Assert (Top.WasDisposed); - Debug.Assert (Top == CachedSessionTokenToplevel); + // This assertion confirm if the Current was already disposed + Debug.Assert (Current.WasDisposed); + Debug.Assert (Current == CachedSessionTokenToplevel); } #endif - lock (TopLevels) + lock (SessionStack) { - if (Top is { } && toplevel != Top && !TopLevels.Contains (Top)) + if (Current is { } && toplevel != Current && !SessionStack.Contains (Current)) { - // If Top was already disposed and isn't on the Toplevels Stack, + // If Current was already disposed and isn't on the Toplevels Stack, // clean it up here if is the same as _CachedSessionTokenToplevel - if (Top == CachedSessionTokenToplevel) + if (Current == CachedSessionTokenToplevel) { - Top = null; + Current = null; } else { // Probably this will never hit - throw new ObjectDisposedException (Top.GetType ().FullName); + throw new ObjectDisposedException (Current.GetType ().FullName); } } @@ -67,56 +66,58 @@ public partial class ApplicationImpl if (string.IsNullOrEmpty (toplevel.Id)) { var count = 1; - var id = (TopLevels.Count + count).ToString (); + var id = (SessionStack.Count + count).ToString (); - while (TopLevels.Count > 0 && TopLevels.FirstOrDefault (x => x.Id == id) is { }) + while (SessionStack.Count > 0 && SessionStack.FirstOrDefault (x => x.Id == id) is { }) { count++; - id = (TopLevels.Count + count).ToString (); + id = (SessionStack.Count + count).ToString (); } - toplevel.Id = (TopLevels.Count + count).ToString (); + toplevel.Id = (SessionStack.Count + count).ToString (); - TopLevels.Push (toplevel); + SessionStack.Push (toplevel); } else { - Toplevel? dup = TopLevels.FirstOrDefault (x => x.Id == toplevel.Id); + Toplevel? dup = SessionStack.FirstOrDefault (x => x.Id == toplevel.Id); if (dup is null) { - TopLevels.Push (toplevel); + SessionStack.Push (toplevel); } } } - if (Top is null) + if (Current is null) { - Top = toplevel; + toplevel.App = this; + Current = toplevel; } - if ((Top?.Modal == false && toplevel.Modal) - || (Top?.Modal == false && !toplevel.Modal) - || (Top?.Modal == true && toplevel.Modal)) + if ((Current?.Modal == false && toplevel.Modal) + || (Current?.Modal == false && !toplevel.Modal) + || (Current?.Modal == true && toplevel.Modal)) { if (toplevel.Visible) { - if (Top is { HasFocus: true }) + if (Current is { HasFocus: true }) { - Top.HasFocus = false; + Current.HasFocus = false; } - // Force leave events for any entered views in the old Top - if (Mouse.GetLastMousePosition () is { }) + // Force leave events for any entered views in the old Current + if (Mouse.LastMousePosition is { }) { - Mouse.RaiseMouseEnterLeaveEvents (Mouse.GetLastMousePosition ()!.Value, new ()); + Mouse.RaiseMouseEnterLeaveEvents (Mouse.LastMousePosition!.Value, new ()); } - Top?.OnDeactivate (toplevel); - Toplevel previousTop = Top!; + Current?.OnDeactivate (toplevel); + Toplevel previousTop = Current!; - Top = toplevel; - Top.OnActivate (previousTop); + Current = toplevel; + Current.App = this; + Current.OnActivate (previousTop); } } @@ -135,7 +136,7 @@ public partial class ApplicationImpl toplevel.OnLoaded (); - Instance.LayoutAndDraw (true); + LayoutAndDraw (true); if (PositionCursor ()) { @@ -156,18 +157,18 @@ public partial class ApplicationImpl /// [RequiresUnreferencedCode ("AOT")] [RequiresDynamicCode ("AOT")] - public Toplevel Run (Func? errorHandler = null, string? driver = null) { return Run (errorHandler, driver); } + public Toplevel Run (Func? errorHandler = null, string? driverName = null) { return Run (errorHandler, driverName); } /// [RequiresUnreferencedCode ("AOT")] [RequiresDynamicCode ("AOT")] - public TView Run (Func? errorHandler = null, string? driver = null) + public TView Run (Func? errorHandler = null, string? driverName = null) where TView : Toplevel, new () { if (!Initialized) { // Init() has NOT been called. Auto-initialize as per interface contract. - Init (null, driver); + Init (driverName); } TView top = new (); @@ -193,15 +194,15 @@ public partial class ApplicationImpl throw new InvalidOperationException ("Driver was inexplicably null when trying to Run view"); } - Top = view; + Current = view; - SessionToken rs = Application.Begin (view); + SessionToken rs = Begin (view); - Top.Running = true; + Current.Running = true; var firstIteration = true; - while (TopLevels.TryPeek (out Toplevel? found) && found == view && view.Running) + while (SessionStack.TryPeek (out Toplevel? found) && found == view && view.Running) { if (Coordinator is null) { @@ -220,7 +221,7 @@ public partial class ApplicationImpl } Logging.Information ("Run - Calling End"); - Application.End (rs); + End (rs); } /// @@ -233,11 +234,11 @@ public partial class ApplicationImpl ApplicationPopover.HideWithQuitCommand (visiblePopover); } - sessionToken.Toplevel.OnUnloaded (); + sessionToken.Toplevel?.OnUnloaded (); // End the Session // First, take it off the Toplevel Stack - if (TopLevels.TryPop (out Toplevel? topOfStack)) + if (SessionStack.TryPop (out Toplevel? topOfStack)) { if (topOfStack != sessionToken.Toplevel) { @@ -250,10 +251,11 @@ public partial class ApplicationImpl // Notify that it is closing sessionToken.Toplevel?.OnClosed (sessionToken.Toplevel); - if (TopLevels.TryPeek (out Toplevel? newTop)) + if (SessionStack.TryPeek (out Toplevel? newTop)) { - Top = newTop; - Top?.SetNeedsDraw (); + newTop.App = this; + Current = newTop; + Current?.SetNeedsDraw (); } if (sessionToken.Toplevel is { HasFocus: true }) @@ -261,9 +263,9 @@ public partial class ApplicationImpl sessionToken.Toplevel.HasFocus = false; } - if (Top is { HasFocus: false }) + if (Current is { HasFocus: false }) { - Top.SetFocus (); + Current.SetFocus (); } CachedSessionTokenToplevel = sessionToken.Toplevel; @@ -283,9 +285,9 @@ public partial class ApplicationImpl /// public void RequestStop (Toplevel? top) { - Logging.Trace ($"Top: '{(top is { } ? top : "null")}'"); + Logging.Trace ($"Current: '{(top is { } ? top : "null")}'"); - top ??= Top; + top ??= Current; if (top == null) { @@ -322,12 +324,12 @@ public partial class ApplicationImpl public bool RemoveTimeout (object token) { return _timedEvents.Remove (token); } /// - public void Invoke (Action action) + public void Invoke (Action? action) { // If we are already on the main UI thread - if (Top is { Running: true } && MainThreadId == Thread.CurrentThread.ManagedThreadId) + if (Current is { Running: true } && MainThreadId == Thread.CurrentThread.ManagedThreadId) { - action (); + action?.Invoke (this); return; } @@ -336,7 +338,30 @@ public partial class ApplicationImpl TimeSpan.Zero, () => { - action (); + action?.Invoke (this); + + return false; + } + ); + } + + + /// + public void Invoke (Action action) + { + // If we are already on the main UI thread + if (Current is { Running: true } && MainThreadId == Thread.CurrentThread.ManagedThreadId) + { + action?.Invoke (); + + return; + } + + _timedEvents.Add ( + TimeSpan.Zero, + () => + { + action?.Invoke (); return false; } diff --git a/Terminal.Gui/App/ApplicationImpl.Screen.cs b/Terminal.Gui/App/ApplicationImpl.Screen.cs index 3c2f32cb6..b5a930794 100644 --- a/Terminal.Gui/App/ApplicationImpl.Screen.cs +++ b/Terminal.Gui/App/ApplicationImpl.Screen.cs @@ -1,4 +1,3 @@ -#nullable enable namespace Terminal.Gui.App; @@ -45,6 +44,11 @@ public partial class ApplicationImpl /// public bool PositionCursor () { + if (Driver is null) + { + return false; + } + // Find the most focused view and position the cursor there. View? mostFocused = Navigation?.GetFocused (); @@ -66,7 +70,7 @@ public partial class ApplicationImpl Rectangle mostFocusedViewport = mostFocused.ViewportToScreen (mostFocused.Viewport with { Location = Point.Empty }); Rectangle superViewViewport = - mostFocused.SuperView?.ViewportToScreen (mostFocused.SuperView.Viewport with { Location = Point.Empty }) ?? Driver!.Screen; + mostFocused.SuperView?.ViewportToScreen (mostFocused.SuperView.Viewport with { Location = Point.Empty }) ?? Driver.Screen; if (!superViewViewport.IntersectsWith (mostFocusedViewport)) { @@ -133,7 +137,7 @@ public partial class ApplicationImpl ScreenChanged?.Invoke (this, new (screen)); - foreach (Toplevel t in TopLevels) + foreach (Toplevel t in SessionStack) { t.OnSizeChanging (new (screen.Size)); t.SetNeedsLayout (); @@ -147,7 +151,7 @@ public partial class ApplicationImpl /// public void LayoutAndDraw (bool forceRedraw = false) { - List tops = [.. TopLevels]; + List tops = [.. SessionStack]; if (Popover?.GetActivePopover () as View is { Visible: true } visiblePopover) { @@ -169,9 +173,12 @@ public partial class ApplicationImpl Driver?.ClearContents (); } - View.SetClipToScreen (); - View.Draw (tops, neededLayout || forceRedraw); - View.SetClipToScreen (); - Driver?.Refresh (); + if (Driver is { }) + { + Driver.Clip = new (Screen); + View.Draw (tops, neededLayout || forceRedraw); + Driver.Clip = new (Screen); + Driver?.Refresh (); + } } } diff --git a/Terminal.Gui/App/ApplicationImpl.cs b/Terminal.Gui/App/ApplicationImpl.cs index d3b0277ba..bcedf0b14 100644 --- a/Terminal.Gui/App/ApplicationImpl.cs +++ b/Terminal.Gui/App/ApplicationImpl.cs @@ -1,4 +1,3 @@ -#nullable enable using System.Collections.Concurrent; namespace Terminal.Gui.App; @@ -10,9 +9,9 @@ namespace Terminal.Gui.App; public partial class ApplicationImpl : IApplication { /// - /// Creates a new instance of the Application backend. + /// INTERNAL: Creates a new instance of the Application backend. /// - public ApplicationImpl () { } + internal ApplicationImpl () { } /// /// INTERNAL: Creates a new instance of the Application backend. @@ -22,22 +21,22 @@ public partial class ApplicationImpl : IApplication #region Singleton - // Private static readonly Lazy instance of Application - private static Lazy _lazyInstance = new (() => new ApplicationImpl ()); - /// - /// Change the singleton implementation, should not be called except before application - /// startup. This method lets you provide alternative implementations of core static gateway - /// methods of . + /// Configures the singleton instance of to use the specified backend implementation. /// - /// - public static void ChangeInstance (IApplication? newApplication) { _lazyInstance = new (newApplication!); } + /// + public static void SetInstance (IApplication? app) + { + _instance = app; + } + + // Private static readonly Lazy instance of Application + private static IApplication? _instance; /// /// Gets the currently configured backend implementation of gateway methods. - /// Change to your own implementation by using (before init). /// - public static IApplication Instance => _lazyInstance.Value; + public static IApplication Instance => _instance ??= new ApplicationImpl (); #endregion Singleton @@ -57,7 +56,7 @@ public partial class ApplicationImpl : IApplication { if (_mouse is null) { - _mouse = new MouseImpl { Application = this }; + _mouse = new MouseImpl { App = this }; } return _mouse; @@ -66,7 +65,6 @@ public partial class ApplicationImpl : IApplication } private IKeyboard? _keyboard; - private bool _stopAfterFirstIteration; /// /// Handles keyboard input and key bindings at the Application level @@ -77,7 +75,7 @@ public partial class ApplicationImpl : IApplication { if (_keyboard is null) { - _keyboard = new KeyboardImpl { Application = this }; + _keyboard = new KeyboardImpl { App = this }; } return _keyboard; @@ -89,22 +87,67 @@ public partial class ApplicationImpl : IApplication #region View Management - /// - public ApplicationPopover? Popover { get; set; } + private ApplicationPopover? _popover; /// - public ApplicationNavigation? Navigation { get; set; } + public ApplicationPopover? Popover + { + get + { + if (_popover is null) + { + _popover = new () { App = this }; + } + + return _popover; + } + set => _popover = value; + } + + private ApplicationNavigation? _navigation; /// - public Toplevel? Top { get; set; } + public ApplicationNavigation? Navigation + { + get + { + if (_navigation is null) + { + _navigation = new () { App = this }; + } - // BUGBUG: Technically, this is not the full lst of TopLevels. There be dragons here, e.g. see how Toplevel.Id is used. What + return _navigation; + } + set => _navigation = value ?? throw new ArgumentNullException (nameof (value)); + } + + private Toplevel? _current; /// - public ConcurrentStack TopLevels { get; } = new (); + public Toplevel? Current + { + get => _current; + set + { + _current = value; + + if (_current is { }) + { + _current.App = this; + } + } + } + + // BUGBUG: Technically, this is not the full lst of sessions. There be dragons here, e.g. see how Toplevel.Id is used. What + + /// + public ConcurrentStack SessionStack { get; } = new (); /// public Toplevel? CachedSessionTokenToplevel { get; set; } #endregion View Management + + /// + public new string ToString () => Driver?.ToString () ?? string.Empty; } diff --git a/Terminal.Gui/App/ApplicationNavigation.cs b/Terminal.Gui/App/ApplicationNavigation.cs index 4a64b037b..d0ef40d9c 100644 --- a/Terminal.Gui/App/ApplicationNavigation.cs +++ b/Terminal.Gui/App/ApplicationNavigation.cs @@ -1,4 +1,3 @@ -#nullable enable using System.Diagnostics; @@ -17,6 +16,11 @@ public class ApplicationNavigation // TODO: Move navigation key bindings here from AddApplicationKeyBindings } + /// + /// The instance used by this instance. + /// + public IApplication? App { get; set; } + private View? _focused; /// @@ -105,10 +109,10 @@ public class ApplicationNavigation /// public bool AdvanceFocus (NavigationDirection direction, TabBehavior? behavior) { - if (Application.Popover?.GetActivePopover () as View is { Visible: true } visiblePopover) + if (App?.Popover?.GetActivePopover () as View is { Visible: true } visiblePopover) { return visiblePopover.AdvanceFocus (direction, behavior); } - return Application.Top is { } && Application.Top.AdvanceFocus (direction, behavior); + return App?.Current is { } && App.Current.AdvanceFocus (direction, behavior); } } diff --git a/Terminal.Gui/App/ApplicationPopover.cs b/Terminal.Gui/App/ApplicationPopover.cs index 69430dbab..72f9957df 100644 --- a/Terminal.Gui/App/ApplicationPopover.cs +++ b/Terminal.Gui/App/ApplicationPopover.cs @@ -1,12 +1,11 @@ -#nullable enable using System.Diagnostics; namespace Terminal.Gui.App; /// -/// Helper class for support of views for . Held by -/// +/// Helper class for support of views for . Held by +/// /// public sealed class ApplicationPopover : IDisposable { @@ -15,6 +14,11 @@ public sealed class ApplicationPopover : IDisposable /// public ApplicationPopover () { } + /// + /// The instance used by this instance. + /// + public IApplication? App { get; set; } + private readonly List _popovers = []; /// @@ -35,10 +39,15 @@ public sealed class ApplicationPopover : IDisposable /// , after it has been registered. public IPopover? Register (IPopover? popover) { - if (popover is { } && !_popovers.Contains (popover)) + if (popover is { } && !IsRegistered (popover)) { - // When created, set IPopover.Toplevel to the current Application.Top - popover.Toplevel ??= Application.Top; + // When created, set IPopover.Toplevel to the current Application.Current + popover.Current ??= App?.Current; + + if (popover is View popoverView) + { + popoverView.App = App; + } _popovers.Add (popover); } @@ -46,6 +55,13 @@ public sealed class ApplicationPopover : IDisposable return popover; } + /// + /// Indicates whether a popover has been registered or not. + /// + /// + /// + public bool IsRegistered (IPopover? popover) => popover is { } && _popovers.Contains (popover); + /// /// De-registers with the application. Use this to remove the popover and it's /// keyboard bindings from the application. @@ -59,7 +75,7 @@ public sealed class ApplicationPopover : IDisposable /// public bool DeRegister (IPopover? popover) { - if (popover is null || !_popovers.Contains (popover)) + if (popover is null || !IsRegistered (popover)) { return false; } @@ -100,9 +116,14 @@ public sealed class ApplicationPopover : IDisposable /// public void Show (IPopover? popover) { + if (!IsRegistered (popover)) + { + throw new InvalidOperationException (@"Popovers must be registered before being shown."); + } // If there's an existing popover, hide it. if (_activePopover is View popoverView) { + popoverView.App = App; popoverView.Visible = false; _activePopover = null; } @@ -120,9 +141,6 @@ public sealed class ApplicationPopover : IDisposable throw new InvalidOperationException ("Popovers must have a key binding for Command.Quit."); } - - Register (popover); - if (!newPopover.IsInitialized) { newPopover.BeginInit (); @@ -148,7 +166,7 @@ public sealed class ApplicationPopover : IDisposable { _activePopover = null; popoverView.Visible = false; - Application.Top?.SetNeedsDraw (); + popoverView.App?.Current?.SetNeedsDraw (); } } @@ -177,7 +195,7 @@ public sealed class ApplicationPopover : IDisposable internal bool DispatchKeyDown (Key key) { // Do active first - Active gets all key down events. - var activePopover = GetActivePopover () as View; + View? activePopover = GetActivePopover () as View; if (activePopover is { Visible: true }) { @@ -197,13 +215,14 @@ public sealed class ApplicationPopover : IDisposable { if (popover == activePopover || popover is not View popoverView - || (popover.Toplevel is { } && popover.Toplevel != Application.Top)) + || (popover.Current is { } && popover.Current != App?.Current)) { continue; } // hotKeyHandled = popoverView.InvokeCommandsBoundToHotKey (key); //Logging.Debug ($"Inactive - Calling NewKeyDownEvent ({key}) on {popoverView.Title}"); + popoverView.App ??= App; hotKeyHandled = popoverView.NewKeyDownEvent (key); if (hotKeyHandled is true) diff --git a/Terminal.Gui/App/CWP/CWPEventHelper.cs b/Terminal.Gui/App/CWP/CWPEventHelper.cs index d85d184d5..4840a358c 100644 --- a/Terminal.Gui/App/CWP/CWPEventHelper.cs +++ b/Terminal.Gui/App/CWP/CWPEventHelper.cs @@ -1,4 +1,3 @@ -#nullable enable namespace Terminal.Gui.App; using System; @@ -53,4 +52,4 @@ public static class CWPEventHelper eventHandler.Invoke (null, args); return args.Handled; } -} \ No newline at end of file +} diff --git a/Terminal.Gui/App/CWP/CWPPropertyHelper.cs b/Terminal.Gui/App/CWP/CWPPropertyHelper.cs index 2ea94ce97..09bcd7fa0 100644 --- a/Terminal.Gui/App/CWP/CWPPropertyHelper.cs +++ b/Terminal.Gui/App/CWP/CWPPropertyHelper.cs @@ -1,7 +1,5 @@ namespace Terminal.Gui.App; -#nullable enable - /// /// Provides helper methods for executing property change workflows in the Cancellable Work Pattern (CWP). /// diff --git a/Terminal.Gui/App/CWP/CWPWorkflowHelper.cs b/Terminal.Gui/App/CWP/CWPWorkflowHelper.cs index 401f17fb8..4c0328589 100644 --- a/Terminal.Gui/App/CWP/CWPWorkflowHelper.cs +++ b/Terminal.Gui/App/CWP/CWPWorkflowHelper.cs @@ -1,4 +1,3 @@ -#nullable enable namespace Terminal.Gui.App; using System; @@ -126,4 +125,4 @@ public static class CWPWorkflowHelper } return args.Result!; } -} \ No newline at end of file +} diff --git a/Terminal.Gui/App/CWP/CancelEventArgs.cs b/Terminal.Gui/App/CWP/CancelEventArgs.cs index 7378b722a..422fa5323 100644 --- a/Terminal.Gui/App/CWP/CancelEventArgs.cs +++ b/Terminal.Gui/App/CWP/CancelEventArgs.cs @@ -1,4 +1,3 @@ -#nullable enable using System.ComponentModel; namespace Terminal.Gui.App; diff --git a/Terminal.Gui/App/CWP/EventArgs.cs b/Terminal.Gui/App/CWP/EventArgs.cs index fe7644264..edd1cc450 100644 --- a/Terminal.Gui/App/CWP/EventArgs.cs +++ b/Terminal.Gui/App/CWP/EventArgs.cs @@ -1,4 +1,3 @@ -#nullable enable namespace Terminal.Gui.App; #pragma warning disable CS1711 diff --git a/Terminal.Gui/App/CWP/ResultEventArgs.cs b/Terminal.Gui/App/CWP/ResultEventArgs.cs index d75627c3b..e8a3f67c0 100644 --- a/Terminal.Gui/App/CWP/ResultEventArgs.cs +++ b/Terminal.Gui/App/CWP/ResultEventArgs.cs @@ -1,4 +1,3 @@ -#nullable enable namespace Terminal.Gui.App; using System; @@ -42,4 +41,4 @@ public class ResultEventArgs Result = result; } } -#pragma warning restore CS1711 \ No newline at end of file +#pragma warning restore CS1711 diff --git a/Terminal.Gui/App/CWP/ValueChangedEventArgs.cs b/Terminal.Gui/App/CWP/ValueChangedEventArgs.cs index d04c42825..880c59245 100644 --- a/Terminal.Gui/App/CWP/ValueChangedEventArgs.cs +++ b/Terminal.Gui/App/CWP/ValueChangedEventArgs.cs @@ -1,4 +1,3 @@ -#nullable enable namespace Terminal.Gui.App; /// diff --git a/Terminal.Gui/App/CWP/ValueChangingEventArgs.cs b/Terminal.Gui/App/CWP/ValueChangingEventArgs.cs index fed087b8c..15d4688b2 100644 --- a/Terminal.Gui/App/CWP/ValueChangingEventArgs.cs +++ b/Terminal.Gui/App/CWP/ValueChangingEventArgs.cs @@ -1,4 +1,3 @@ -#nullable enable namespace Terminal.Gui.App; /// @@ -41,4 +40,4 @@ public class ValueChangingEventArgs CurrentValue = currentValue; NewValue = newValue; } -} \ No newline at end of file +} diff --git a/Terminal.Gui/App/Clipboard/Clipboard.cs b/Terminal.Gui/App/Clipboard/Clipboard.cs index f8cf39892..0db013351 100644 --- a/Terminal.Gui/App/Clipboard/Clipboard.cs +++ b/Terminal.Gui/App/Clipboard/Clipboard.cs @@ -1,4 +1,3 @@ -#nullable enable namespace Terminal.Gui.App; /// Provides cut, copy, and paste support for the OS clipboard. @@ -31,7 +30,7 @@ public static class Clipboard if (IsSupported) { // throw new InvalidOperationException ($"{Application.Driver?.GetType ().Name}.GetClipboardData returned null instead of string.Empty"); - string? clipData = Application.Driver?.Clipboard?.GetClipboardData () ?? string.Empty; + string clipData = Application.Driver?.Clipboard?.GetClipboardData () ?? string.Empty; _contents = clipData; } @@ -66,4 +65,4 @@ public static class Clipboard /// Returns true if the environmental dependencies are in place to interact with the OS clipboard. /// public static bool IsSupported => Application.Driver?.Clipboard?.IsSupported ?? false; -} \ No newline at end of file +} diff --git a/Terminal.Gui/App/Clipboard/ClipboardBase.cs b/Terminal.Gui/App/Clipboard/ClipboardBase.cs index 97cfec61e..46a1f26bf 100644 --- a/Terminal.Gui/App/Clipboard/ClipboardBase.cs +++ b/Terminal.Gui/App/Clipboard/ClipboardBase.cs @@ -1,3 +1,4 @@ +#nullable disable using System.Diagnostics; namespace Terminal.Gui.App; diff --git a/Terminal.Gui/App/Clipboard/ClipboardProcessRunner.cs b/Terminal.Gui/App/Clipboard/ClipboardProcessRunner.cs index 214b5337d..70c471d74 100644 --- a/Terminal.Gui/App/Clipboard/ClipboardProcessRunner.cs +++ b/Terminal.Gui/App/Clipboard/ClipboardProcessRunner.cs @@ -1,4 +1,3 @@ -#nullable enable using System.Diagnostics; namespace Terminal.Gui.App; @@ -45,7 +44,6 @@ internal static class ClipboardProcessRunner CreateNoWindow = true }; - TaskCompletionSource eventHandled = new (); process.Start (); if (!string.IsNullOrEmpty (input)) diff --git a/Terminal.Gui/App/IApplication.cs b/Terminal.Gui/App/IApplication.cs index 64620f09e..c02563170 100644 --- a/Terminal.Gui/App/IApplication.cs +++ b/Terminal.Gui/App/IApplication.cs @@ -1,5 +1,4 @@ -#nullable enable -using System.Collections.Concurrent; +using System.Collections.Concurrent; using System.Diagnostics.CodeAnalysis; namespace Terminal.Gui.App; @@ -41,14 +40,9 @@ public interface IApplication #region Initialization and Shutdown /// Initializes a new instance of Application. - /// - /// The to use. If neither or - /// are specified the default driver for the platform will be used. - /// /// /// The short name (e.g. "dotnet", "windows", "unix", or "fake") of the - /// to use. If neither or are - /// specified the default driver for the platform will be used. + /// to use. If not specified the default driver for the platform will be used. /// /// /// Call this method once per instance (or after has been called). @@ -61,14 +55,14 @@ public interface IApplication /// has returned) to ensure resources are cleaned up and terminal settings restored. /// /// - /// The function combines and + /// The function combines and /// into a single call. An application can use - /// without explicitly calling . + /// without explicitly calling . /// /// [RequiresUnreferencedCode ("AOT")] [RequiresDynamicCode ("AOT")] - public void Init (IDriver? driver = null, string? driverName = null); + public void Init (string? driverName = null); /// /// This event is raised after the and methods have been called. @@ -137,7 +131,7 @@ public interface IApplication /// stopped, will be called. /// /// Handler for any unhandled exceptions (resumes when returns true, rethrows when null). - /// + /// /// The driver name. If not specified the default driver for the platform will be used. Must be /// if has already been called. /// @@ -154,7 +148,7 @@ public interface IApplication /// [RequiresUnreferencedCode ("AOT")] [RequiresDynamicCode ("AOT")] - public Toplevel Run (Func? errorHandler = null, string? driver = null); + public Toplevel Run (Func? errorHandler = null, string? driverName = null); /// /// Runs a new Session creating a -derived object of type @@ -163,7 +157,7 @@ public interface IApplication /// /// The type of to create and run. /// Handler for any unhandled exceptions (resumes when returns true, rethrows when null). - /// + /// /// The driver name. If not specified the default driver for the platform will be used. Must be /// if has already been called. /// @@ -206,7 +200,7 @@ public interface IApplication /// [RequiresUnreferencedCode ("AOT")] [RequiresDynamicCode ("AOT")] - public TView Run (Func? errorHandler = null, string? driver = null) + public TView Run (Func? errorHandler = null, string? driverName = null) where TView : Toplevel, new (); /// @@ -266,6 +260,17 @@ public interface IApplication /// public event EventHandler? Iteration; + /// Runs on the main UI loop thread. + /// The action to be invoked on the main processing thread. + /// + /// + /// If called from the main thread, the action is executed immediately. Otherwise, it is queued via + /// with and will be executed on the next main loop + /// iteration. + /// + /// + void Invoke (Action? action); + /// Runs on the main UI loop thread. /// The action to be invoked on the main processing thread. /// @@ -296,14 +301,14 @@ public interface IApplication /// /// This will cause to return. /// - /// This is equivalent to calling with as the parameter. + /// This is equivalent to calling with as the parameter. /// /// void RequestStop (); /// Requests that the currently running Session stop. The Session will stop after the current iteration completes. /// - /// The to stop. If , stops the currently running . + /// The to stop. If , stops the currently running . /// /// /// This will cause to return. @@ -351,22 +356,22 @@ public interface IApplication #region Toplevel Management - /// Gets or sets the current Toplevel. + /// Gets or sets the currently active Toplevel. /// /// /// This is set by and cleared by . /// /// - Toplevel? Top { get; set; } + Toplevel? Current { get; set; } - /// Gets the stack of all Toplevels. + /// Gets the stack of all active Toplevel sessions. /// /// /// Toplevels are added to this stack by and removed by /// . /// /// - ConcurrentStack TopLevels { get; } + ConcurrentStack SessionStack { get; } /// /// Caches the Toplevel associated with the current Session. @@ -428,7 +433,7 @@ public interface IApplication /// /// /// This is typically set to when a View's changes and that view - /// has no SuperView (e.g. when is moved or resized). + /// has no SuperView (e.g. when is moved or resized). /// /// /// Automatically reset to after processes it. @@ -509,12 +514,15 @@ public interface IApplication /// returns , the timeout will stop and be removed. /// /// - /// A token that can be used to stop the timeout by calling . + /// Call with the returned value to stop the timeout. /// /// /// /// When the time specified passes, the callback will be invoked on the main UI thread. /// + /// + /// calls StopAll on to remove all timeouts. + /// /// object AddTimeout (TimeSpan time, Func callback); @@ -539,4 +547,10 @@ public interface IApplication ITimedEvents? TimedEvents { get; } #endregion Timeouts + + /// + /// Gets a string representation of the Application as rendered by . + /// + /// A string representation of the Application + public string ToString (); } diff --git a/Terminal.Gui/App/IPopover.cs b/Terminal.Gui/App/IPopover.cs index 7e86ffe77..87218d07a 100644 --- a/Terminal.Gui/App/IPopover.cs +++ b/Terminal.Gui/App/IPopover.cs @@ -1,4 +1,3 @@ -#nullable enable namespace Terminal.Gui.App; @@ -51,12 +50,12 @@ namespace Terminal.Gui.App; public interface IPopover { /// - /// Gets or sets the that this Popover is associated with. If null, it is not associated with + /// Gets or sets the that this Popover is associated with. If null, it is not associated with /// any Toplevel and will receive all keyboard - /// events from the . If set, it will only receive keyboard events the Toplevel would normally + /// events from the . If set, it will only receive keyboard events the Toplevel would normally /// receive. - /// When is called, the is set to the current - /// if not already set. + /// When is called, the is set to the current + /// if not already set. /// - Toplevel? Toplevel { get; set; } + Toplevel? Current { get; set; } } diff --git a/Terminal.Gui/App/Keyboard/IKeyboard.cs b/Terminal.Gui/App/Keyboard/IKeyboard.cs index d0fbd023e..404217308 100644 --- a/Terminal.Gui/App/Keyboard/IKeyboard.cs +++ b/Terminal.Gui/App/Keyboard/IKeyboard.cs @@ -1,10 +1,9 @@ -#nullable enable namespace Terminal.Gui.App; /// /// Defines a contract for managing keyboard input and key bindings at the Application level. /// -/// This interface decouples keyboard handling state from the static class, +/// This interface decouples keyboard handling state from the static class, /// enabling parallelizable unit tests and better testability. /// /// @@ -14,7 +13,7 @@ public interface IKeyboard /// Sets the application instance that this keyboard handler is associated with. /// This provides access to application state without coupling to static Application class. /// - IApplication? Application { get; set; } + IApplication? App { get; set; } /// /// Called when the user presses a key (by the ). Raises the cancelable diff --git a/Terminal.Gui/App/Keyboard/KeyboardImpl.cs b/Terminal.Gui/App/Keyboard/KeyboardImpl.cs index 0741f4c53..e1be672e5 100644 --- a/Terminal.Gui/App/Keyboard/KeyboardImpl.cs +++ b/Terminal.Gui/App/Keyboard/KeyboardImpl.cs @@ -1,12 +1,9 @@ -#nullable enable -using System.Diagnostics; - namespace Terminal.Gui.App; /// /// INTERNAL: Implements to manage keyboard input and key bindings at the Application level. /// -/// This implementation decouples keyboard handling state from the static class, +/// This implementation decouples keyboard handling state from the static class, /// enabling parallelizable unit tests and better testability. /// /// @@ -28,7 +25,7 @@ internal class KeyboardImpl : IKeyboard private readonly Dictionary _commandImplementations = new (); /// - public IApplication? Application { get; set; } + public IApplication? App { get; set; } /// public KeyBindings KeyBindings { get; internal set; } = new (null); @@ -136,16 +133,16 @@ internal class KeyboardImpl : IKeyboard return true; } - if (Application?.Popover?.DispatchKeyDown (key) is true) + if (App?.Popover?.DispatchKeyDown (key) is true) { return true; } - if (Application?.Top is null) + if (App?.Current is null) { - if (Application?.TopLevels is { }) + if (App?.SessionStack is { }) { - foreach (Toplevel topLevel in Application.TopLevels.ToList ()) + foreach (Toplevel topLevel in App.SessionStack.ToList ()) { if (topLevel.NewKeyDownEvent (key)) { @@ -161,7 +158,7 @@ internal class KeyboardImpl : IKeyboard } else { - if (Application.Top.NewKeyDownEvent (key)) + if (App.Current.NewKeyDownEvent (key)) { return true; } @@ -179,7 +176,7 @@ internal class KeyboardImpl : IKeyboard /// public bool RaiseKeyUpEvent (Key key) { - if (Application?.Initialized != true) + if (App?.Initialized != true) { return true; } @@ -194,9 +191,9 @@ internal class KeyboardImpl : IKeyboard // TODO: Add Popover support - if (Application?.TopLevels is { }) + if (App?.SessionStack is { }) { - foreach (Toplevel topLevel in Application.TopLevels.ToList ()) + foreach (Toplevel topLevel in App.SessionStack.ToList ()) { if (topLevel.NewKeyUpEvent (key)) { @@ -294,7 +291,7 @@ internal class KeyboardImpl : IKeyboard Command.Quit, () => { - Application?.RequestStop (); + App?.RequestStop (); return true; } @@ -303,32 +300,32 @@ internal class KeyboardImpl : IKeyboard Command.Suspend, () => { - Application?.Driver?.Suspend (); + App?.Driver?.Suspend (); return true; } ); AddCommand ( Command.NextTabStop, - () => Application?.Navigation?.AdvanceFocus (NavigationDirection.Forward, TabBehavior.TabStop)); + () => App?.Navigation?.AdvanceFocus (NavigationDirection.Forward, TabBehavior.TabStop)); AddCommand ( Command.PreviousTabStop, - () => Application?.Navigation?.AdvanceFocus (NavigationDirection.Backward, TabBehavior.TabStop)); + () => App?.Navigation?.AdvanceFocus (NavigationDirection.Backward, TabBehavior.TabStop)); AddCommand ( Command.NextTabGroup, - () => Application?.Navigation?.AdvanceFocus (NavigationDirection.Forward, TabBehavior.TabGroup)); + () => App?.Navigation?.AdvanceFocus (NavigationDirection.Forward, TabBehavior.TabGroup)); AddCommand ( Command.PreviousTabGroup, - () => Application?.Navigation?.AdvanceFocus (NavigationDirection.Backward, TabBehavior.TabGroup)); + () => App?.Navigation?.AdvanceFocus (NavigationDirection.Backward, TabBehavior.TabGroup)); AddCommand ( Command.Refresh, () => { - Application?.LayoutAndDraw (true); + App?.LayoutAndDraw (true); return true; } @@ -338,7 +335,7 @@ internal class KeyboardImpl : IKeyboard Command.Arrange, () => { - View? viewToArrange = Application?.Navigation?.GetFocused (); + View? viewToArrange = App?.Navigation?.GetFocused (); // Go up the superview hierarchy and find the first that is not ViewArrangement.Fixed while (viewToArrange is { SuperView: { }, Arrangement: ViewArrangement.Fixed }) diff --git a/Terminal.Gui/App/MainLoop/ApplicationMainLoop.cs b/Terminal.Gui/App/MainLoop/ApplicationMainLoop.cs index 69a16bd49..df03c3187 100644 --- a/Terminal.Gui/App/MainLoop/ApplicationMainLoop.cs +++ b/Terminal.Gui/App/MainLoop/ApplicationMainLoop.cs @@ -1,8 +1,5 @@ -#nullable enable -using System; using System.Collections.Concurrent; using System.Diagnostics; -using Terminal.Gui.Drivers; namespace Terminal.Gui.App; @@ -30,6 +27,9 @@ public class ApplicationMainLoop : IApplicationMainLoop + public IApplication? App { get; private set; } + /// public ITimedEvents TimedEvents { @@ -82,7 +82,7 @@ public class ApplicationMainLoop : IApplicationMainLoop - /// Handles raising events and setting required draw status etc when changes + /// Handles raising events and setting required draw status etc when changes /// public IToplevelTransitionManager ToplevelTransitionManager = new ToplevelTransitionManager (); @@ -94,14 +94,17 @@ public class ApplicationMainLoop : IApplicationMainLoop /// /// + /// public void Initialize ( ITimedEvents timedEvents, ConcurrentQueue inputBuffer, IInputProcessor inputProcessor, IOutput consoleOutput, - IComponentFactory componentFactory + IComponentFactory componentFactory, + IApplication? app ) { + App = app; InputQueue = inputBuffer; Output = consoleOutput; InputProcessor = inputProcessor; @@ -116,10 +119,10 @@ public class ApplicationMainLoop : IApplicationMainLoop public void Iteration () { - Application.RaiseIteration (); + App?.RaiseIteration (); DateTime dt = DateTime.Now; - int timeAllowed = 1000 / Math.Max(1,(int)Application.MaximumIterationsPerSecond); + int timeAllowed = 1000 / Math.Max (1, (int)Application.MaximumIterationsPerSecond); IterationImpl (); @@ -139,14 +142,14 @@ public class ApplicationMainLoop : IApplicationMainLoop : IApplicationMainLoop : IApplicationMainLoop : IApplicationMainLoopType of raw input events processed by the loop, e.g. for cross-platform .NET driver public interface IApplicationMainLoop : IDisposable where TInputRecord : struct { + /// + /// The Application this loop is associated with. + /// + public IApplication? App { get; } + /// /// Gets the implementation that manages user-defined timeouts and periodic events. /// @@ -73,6 +77,7 @@ public interface IApplicationMainLoop : IDisposable where TInputRe /// The factory for creating driver-specific components. Used here to create the /// that tracks terminal size changes. /// + /// /// /// /// This method is called by during application startup @@ -98,7 +103,8 @@ public interface IApplicationMainLoop : IDisposable where TInputRe ConcurrentQueue inputQueue, IInputProcessor inputProcessor, IOutput output, - IComponentFactory componentFactory + IComponentFactory componentFactory, + IApplication? app ); /// diff --git a/Terminal.Gui/App/MainLoop/IMainLoopCoordinator.cs b/Terminal.Gui/App/MainLoop/IMainLoopCoordinator.cs index e08b2a742..c5321a2ea 100644 --- a/Terminal.Gui/App/MainLoop/IMainLoopCoordinator.cs +++ b/Terminal.Gui/App/MainLoop/IMainLoopCoordinator.cs @@ -16,6 +16,7 @@ public interface IMainLoopCoordinator /// /// Initializes all required subcomponents and starts the input thread. /// + /// /// /// This method: /// @@ -25,7 +26,7 @@ public interface IMainLoopCoordinator /// /// /// A task that completes when initialization is done - public Task StartInputTaskAsync (); + public Task StartInputTaskAsync (IApplication? app); /// /// Stops the input thread and performs cleanup. diff --git a/Terminal.Gui/App/MainLoop/MainLoopCoordinator.cs b/Terminal.Gui/App/MainLoop/MainLoopCoordinator.cs index 45fd2a7d3..58b54c94e 100644 --- a/Terminal.Gui/App/MainLoop/MainLoopCoordinator.cs +++ b/Terminal.Gui/App/MainLoop/MainLoopCoordinator.cs @@ -46,24 +46,25 @@ internal class MainLoopCoordinator : IMainLoopCoordinator where TI private readonly ITimedEvents _timedEvents; private readonly SemaphoreSlim _startupSemaphore = new (0, 1); - private IInput _input; - private Task _inputTask; - private IOutput _output; - private DriverImpl _driver; + private IInput? _input; + private Task? _inputTask; + private IOutput? _output; + private DriverImpl? _driver; private bool _stopCalled; /// /// Starts the input loop thread in separate task (returning immediately). /// - public async Task StartInputTaskAsync () + /// The instance that is running the input loop. + public async Task StartInputTaskAsync (IApplication? app) { Logging.Trace ("Booting... ()"); - _inputTask = Task.Run (RunInput); + _inputTask = Task.Run (() => RunInput (app)); // Main loop is now booted on same thread as rest of users application - BootMainLoop (); + BootMainLoop (app); // Wait asynchronously for the semaphore or task failure. Task waitForSemaphore = _startupSemaphore.WaitAsync (); @@ -107,13 +108,13 @@ internal class MainLoopCoordinator : IMainLoopCoordinator where TI _stopCalled = true; _runCancellationTokenSource.Cancel (); - _output.Dispose (); + _output?.Dispose (); // Wait for input infinite loop to exit - _inputTask.Wait (); + _inputTask?.Wait (); } - private void BootMainLoop () + private void BootMainLoop (IApplication? app) { //Logging.Trace ($"_inputProcessor: {_inputProcessor}, _output: {_output}, _componentFactory: {_componentFactory}"); @@ -121,13 +122,13 @@ internal class MainLoopCoordinator : IMainLoopCoordinator where TI { // Instance must be constructed on the thread in which it is used. _output = _componentFactory.CreateOutput (); - _loop.Initialize (_timedEvents, _inputQueue, _inputProcessor, _output, _componentFactory); + _loop.Initialize (_timedEvents, _inputQueue, _inputProcessor, _output, _componentFactory, app); - BuildDriverIfPossible (); + BuildDriverIfPossible (app); } } - private void BuildDriverIfPossible () + private void BuildDriverIfPossible (IApplication? app) { if (_input != null && _output != null) @@ -139,7 +140,7 @@ internal class MainLoopCoordinator : IMainLoopCoordinator where TI _loop.AnsiRequestScheduler, _loop.SizeMonitor); - Application.Driver = _driver; + app!.Driver = _driver; _startupSemaphore.Release (); Logging.Trace ($"Driver: _input: {_input}, _output: {_output}"); @@ -149,7 +150,8 @@ internal class MainLoopCoordinator : IMainLoopCoordinator where TI /// /// INTERNAL: Runs the IInput read loop on a new thread called the "Input Thread". /// - private void RunInput () + /// + private void RunInput (IApplication? app) { try { @@ -165,7 +167,7 @@ internal class MainLoopCoordinator : IMainLoopCoordinator where TI impl.InputImpl = _input; } - BuildDriverIfPossible (); + BuildDriverIfPossible (app); } try diff --git a/Terminal.Gui/App/MainLoop/MainLoopSyncContext.cs b/Terminal.Gui/App/MainLoop/MainLoopSyncContext.cs index f67f5ec81..b9375ccca 100644 --- a/Terminal.Gui/App/MainLoop/MainLoopSyncContext.cs +++ b/Terminal.Gui/App/MainLoop/MainLoopSyncContext.cs @@ -1,42 +1,43 @@ +#nullable disable namespace Terminal.Gui.App; -/// -/// provides the sync context set while executing code in Terminal.Gui, to let -/// users use async/await on their code -/// -internal sealed class MainLoopSyncContext : SynchronizationContext -{ - public override SynchronizationContext CreateCopy () { return new MainLoopSyncContext (); } +///// +///// provides the sync context set while executing code in Terminal.Gui, to let +///// users use async/await on their code +///// +//internal sealed class MainLoopSyncContext : SynchronizationContext +//{ +// public override SynchronizationContext CreateCopy () { return new MainLoopSyncContext (); } - public override void Post (SendOrPostCallback d, object state) - { - // Queue the task using the modern architecture - ApplicationImpl.Instance.Invoke (() => { d (state); }); - } +// public override void Post (SendOrPostCallback d, object state) +// { +// // Queue the task using the modern architecture +// ApplicationImpl.Instance.Invoke (() => { d (state); }); +// } - //_mainLoop.Driver.Wakeup (); - public override void Send (SendOrPostCallback d, object state) - { - if (Thread.CurrentThread.ManagedThreadId == Application.MainThreadId) - { - d (state); - } - else - { - var wasExecuted = false; +// //_mainLoop.Driver.Wakeup (); +// public override void Send (SendOrPostCallback d, object state) +// { +// if (Thread.CurrentThread.ManagedThreadId == Application.MainThreadId) +// { +// d (state); +// } +// else +// { +// var wasExecuted = false; - Application.Invoke ( - () => - { - d (state); - wasExecuted = true; - } - ); +// ApplicationImpl.Instance.Invoke ( +// () => +// { +// d (state); +// wasExecuted = true; +// } +// ); - while (!wasExecuted) - { - Thread.Sleep (15); - } - } - } -} +// while (!wasExecuted) +// { +// Thread.Sleep (15); +// } +// } +// } +//} diff --git a/Terminal.Gui/App/Mouse/IMouse.cs b/Terminal.Gui/App/Mouse/IMouse.cs index a4bc2c2e8..ed01dbf1b 100644 --- a/Terminal.Gui/App/Mouse/IMouse.cs +++ b/Terminal.Gui/App/Mouse/IMouse.cs @@ -1,4 +1,3 @@ -#nullable enable using System.ComponentModel; namespace Terminal.Gui.App; @@ -6,7 +5,7 @@ namespace Terminal.Gui.App; /// /// Defines a contract for mouse event handling and state management in a Terminal.Gui application. /// -/// This interface allows for decoupling of mouse-related functionality from the static class, +/// This interface allows for decoupling of mouse-related functionality from the static class, /// enabling better testability and parallel test execution. /// /// @@ -16,18 +15,13 @@ public interface IMouse : IMouseGrabHandler /// Sets the application instance that this mouse handler is associated with. /// This provides access to application state without coupling to static Application class. /// - IApplication? Application { get; set; } + IApplication? App { get; set; } /// /// Gets or sets the last known position of the mouse. /// Point? LastMousePosition { get; set; } - /// - /// Gets the most recent position of the mouse. - /// - Point? GetLastMousePosition (); - /// /// Gets or sets whether the mouse is disabled. The mouse is enabled by default. /// diff --git a/Terminal.Gui/App/Mouse/IMouseGrabHandler.cs b/Terminal.Gui/App/Mouse/IMouseGrabHandler.cs index 06fd0e626..31bdebeaf 100644 --- a/Terminal.Gui/App/Mouse/IMouseGrabHandler.cs +++ b/Terminal.Gui/App/Mouse/IMouseGrabHandler.cs @@ -1,4 +1,3 @@ -#nullable enable namespace Terminal.Gui.App; /// diff --git a/Terminal.Gui/App/Mouse/MouseGrabHandler.cs b/Terminal.Gui/App/Mouse/MouseGrabHandler.cs index 3fe7ab689..175d371ed 100644 --- a/Terminal.Gui/App/Mouse/MouseGrabHandler.cs +++ b/Terminal.Gui/App/Mouse/MouseGrabHandler.cs @@ -1,4 +1,3 @@ -#nullable enable namespace Terminal.Gui.App; /// diff --git a/Terminal.Gui/App/Mouse/MouseImpl.cs b/Terminal.Gui/App/Mouse/MouseImpl.cs index 78d662dde..29116db0a 100644 --- a/Terminal.Gui/App/Mouse/MouseImpl.cs +++ b/Terminal.Gui/App/Mouse/MouseImpl.cs @@ -1,13 +1,11 @@ -#nullable enable using System.ComponentModel; -using System.Diagnostics; namespace Terminal.Gui.App; /// /// INTERNAL: Implements to manage mouse event handling and state. /// -/// This class holds all mouse-related state that was previously in the static class, +/// This class holds all mouse-related state that was previously in the static class, /// enabling better testability and parallel test execution. /// /// @@ -19,14 +17,11 @@ internal class MouseImpl : IMouse public MouseImpl () { } /// - public IApplication? Application { get; set; } + public IApplication? App { get; set; } /// public Point? LastMousePosition { get; set; } - /// - public Point? GetLastMousePosition () { return LastMousePosition; } - /// public bool IsMouseDisabled { get; set; } @@ -57,7 +52,7 @@ internal class MouseImpl : IMouse public void RaiseMouseEvent (MouseEventArgs mouseEvent) { //Debug.Assert (App.Application.MainThreadId == Thread.CurrentThread.ManagedThreadId); - if (Application?.Initialized is true) + if (App?.Initialized is true) { // LastMousePosition is only set if the application is initialized. LastMousePosition = mouseEvent.ScreenPosition; @@ -72,9 +67,9 @@ internal class MouseImpl : IMouse //Debug.Assert (mouseEvent.Position == mouseEvent.ScreenPosition); mouseEvent.Position = mouseEvent.ScreenPosition; - List currentViewsUnderMouse = View.GetViewsUnderLocation (mouseEvent.ScreenPosition, ViewportSettingsFlags.TransparentMouse); + List? currentViewsUnderMouse = App?.Current?.GetViewsUnderLocation (mouseEvent.ScreenPosition, ViewportSettingsFlags.TransparentMouse); - View? deepestViewUnderMouse = currentViewsUnderMouse.LastOrDefault (); + View? deepestViewUnderMouse = currentViewsUnderMouse?.LastOrDefault (); if (deepestViewUnderMouse is { }) { @@ -96,7 +91,7 @@ internal class MouseImpl : IMouse // Dismiss the Popover if the user presses mouse outside of it if (mouseEvent.IsPressed - && Application?.Popover?.GetActivePopover () as View is { Visible: true } visiblePopover + && App?.Popover?.GetActivePopover () as View is { Visible: true } visiblePopover && View.IsInHierarchy (visiblePopover, deepestViewUnderMouse, includeAdornments: true) is false) { ApplicationPopover.HideWithQuitCommand (visiblePopover); @@ -119,9 +114,9 @@ internal class MouseImpl : IMouse return; } - // if the mouse is outside the Application.Top or Application.Popover hierarchy, we don't want to + // if the mouse is outside the Application.Current or Popover hierarchy, we don't want to // send the mouse event to the deepest view under the mouse. - if (!View.IsInHierarchy (Application?.Top, deepestViewUnderMouse, true) && !View.IsInHierarchy (Application?.Popover?.GetActivePopover () as View, deepestViewUnderMouse, true)) + if (!View.IsInHierarchy (App?.Current, deepestViewUnderMouse, true) && !View.IsInHierarchy (App?.Popover?.GetActivePopover () as View, deepestViewUnderMouse, true)) { return; } @@ -161,7 +156,10 @@ internal class MouseImpl : IMouse return; } - RaiseMouseEnterLeaveEvents (viewMouseEvent.ScreenPosition, currentViewsUnderMouse); + if (currentViewsUnderMouse is { }) + { + RaiseMouseEnterLeaveEvents (viewMouseEvent.ScreenPosition, currentViewsUnderMouse); + } while (deepestViewUnderMouse.NewMouseEvent (viewMouseEvent) is not true && MouseGrabView is not { }) { diff --git a/Terminal.Gui/App/PopoverBaseImpl.cs b/Terminal.Gui/App/PopoverBaseImpl.cs index 1e9aacb71..41258401f 100644 --- a/Terminal.Gui/App/PopoverBaseImpl.cs +++ b/Terminal.Gui/App/PopoverBaseImpl.cs @@ -1,4 +1,3 @@ -#nullable enable namespace Terminal.Gui.App; @@ -74,8 +73,18 @@ public abstract class PopoverBaseImpl : View, IPopover } } + private Toplevel? _current; + /// - public Toplevel? Toplevel { get; set; } + public Toplevel? Current + { + get => _current; + set + { + _current = value; + App ??= _current?.App; + } + } /// /// Called when the property is changing. @@ -100,14 +109,17 @@ public abstract class PopoverBaseImpl : View, IPopover { // Whenever visible is changing to true, we need to resize; // it's our only chance because we don't get laid out until we're visible - Layout (Application.Screen.Size); + if (App is { }) + { + Layout (App.Screen.Size); + } } else { // Whenever visible is changing to false, we need to reset the focus - if (ApplicationNavigation.IsInHierarchy (this, Application.Navigation?.GetFocused ())) + if (ApplicationNavigation.IsInHierarchy (this, App?.Navigation?.GetFocused ())) { - Application.Navigation?.SetFocused (Application.Top?.MostFocused); + App?.Navigation?.SetFocused (App?.Current?.MostFocused); } } diff --git a/Terminal.Gui/App/SessionToken.cs b/Terminal.Gui/App/SessionToken.cs index d6466ed3f..6505727bb 100644 --- a/Terminal.Gui/App/SessionToken.cs +++ b/Terminal.Gui/App/SessionToken.cs @@ -10,7 +10,7 @@ public class SessionToken : IDisposable public SessionToken (Toplevel view) { Toplevel = view; } /// The belonging to this . - public Toplevel Toplevel { get; internal set; } + public Toplevel? Toplevel { get; internal set; } /// Releases all resource used by the object. /// Call when you are finished using the . diff --git a/Terminal.Gui/App/Timeout/ITimedEvents.cs b/Terminal.Gui/App/Timeout/ITimedEvents.cs index aa9499520..376f71f51 100644 --- a/Terminal.Gui/App/Timeout/ITimedEvents.cs +++ b/Terminal.Gui/App/Timeout/ITimedEvents.cs @@ -1,4 +1,3 @@ -#nullable enable namespace Terminal.Gui.App; /// @@ -49,4 +48,14 @@ public interface ITimedEvents /// for each timeout that is not actively executing. /// SortedList Timeouts { get; } + + /// + /// Gets the timeout for the specified event. + /// + /// The token of the event. + /// The for the event, or if the event is not found. + TimeSpan? GetTimeout (object token); + + /// Stops and removes all timed events. + void StopAll (); } diff --git a/Terminal.Gui/App/Timeout/LogarithmicTimeout.cs b/Terminal.Gui/App/Timeout/LogarithmicTimeout.cs index eacf09607..25690eb24 100644 --- a/Terminal.Gui/App/Timeout/LogarithmicTimeout.cs +++ b/Terminal.Gui/App/Timeout/LogarithmicTimeout.cs @@ -1,3 +1,4 @@ +#nullable disable namespace Terminal.Gui.App; /// Implements a logarithmic increasing timeout. diff --git a/Terminal.Gui/App/Timeout/SmoothAcceleratingTimeout.cs b/Terminal.Gui/App/Timeout/SmoothAcceleratingTimeout.cs index 962ce4b19..7a11dcddc 100644 --- a/Terminal.Gui/App/Timeout/SmoothAcceleratingTimeout.cs +++ b/Terminal.Gui/App/Timeout/SmoothAcceleratingTimeout.cs @@ -1,3 +1,4 @@ +#nullable disable namespace Terminal.Gui.App; /// diff --git a/Terminal.Gui/App/Timeout/TimedEvents.cs b/Terminal.Gui/App/Timeout/TimedEvents.cs index da1dcc5c0..09e008b51 100644 --- a/Terminal.Gui/App/Timeout/TimedEvents.cs +++ b/Terminal.Gui/App/Timeout/TimedEvents.cs @@ -1,4 +1,3 @@ -#nullable enable using System.Diagnostics; namespace Terminal.Gui.App; @@ -145,6 +144,22 @@ public class TimedEvents : ITimedEvents return false; } + /// + public TimeSpan? GetTimeout (object token) + { + lock (_timeoutsLockToken) + { + int idx = _timeouts.IndexOfValue ((token as Timeout)!); + + if (idx == -1) + { + return null; + } + + return _timeouts.Values [idx].Span; + } + } + private void AddTimeout (TimeSpan time, Timeout timeout) { lock (_timeoutsLockToken) @@ -202,7 +217,7 @@ public class TimedEvents : ITimedEvents { if (k < now) { - if (timeout.Callback ()) + if (timeout.Callback! ()) { AddTimeout (timeout.Span, timeout); } @@ -216,4 +231,13 @@ public class TimedEvents : ITimedEvents } } } + + /// + public void StopAll () + { + lock (_timeoutsLockToken) + { + _timeouts.Clear (); + } + } } diff --git a/Terminal.Gui/App/Timeout/Timeout.cs b/Terminal.Gui/App/Timeout/Timeout.cs index c3054869f..226dc9e2f 100644 --- a/Terminal.Gui/App/Timeout/Timeout.cs +++ b/Terminal.Gui/App/Timeout/Timeout.cs @@ -20,7 +20,7 @@ public class Timeout /// rescheduled and invoked again after the same interval. /// If the callback returns , the timeout will be removed and not invoked again. /// - public Func Callback { get; set; } + public Func? Callback { get; set; } /// /// Gets or sets the time interval to wait before invoking the . diff --git a/Terminal.Gui/App/Toplevel/IToplevelTransitionManager.cs b/Terminal.Gui/App/Toplevel/IToplevelTransitionManager.cs index 7b69f8c9b..26c44129b 100644 --- a/Terminal.Gui/App/Toplevel/IToplevelTransitionManager.cs +++ b/Terminal.Gui/App/Toplevel/IToplevelTransitionManager.cs @@ -7,14 +7,16 @@ public interface IToplevelTransitionManager { /// - /// Raises the event on the current top level + /// Raises the event on tahe current top level /// if it has not been raised before now. /// - void RaiseReadyEventIfNeeded (); + /// + void RaiseReadyEventIfNeeded (IApplication? app); /// /// Handles any state change needed when the application top changes e.g. /// setting redraw flags /// - void HandleTopMaybeChanging (); + /// + void HandleTopMaybeChanging (IApplication? app); } diff --git a/Terminal.Gui/App/Toplevel/ToplevelTransitionManager.cs b/Terminal.Gui/App/Toplevel/ToplevelTransitionManager.cs index 282dde040..10166b450 100644 --- a/Terminal.Gui/App/Toplevel/ToplevelTransitionManager.cs +++ b/Terminal.Gui/App/Toplevel/ToplevelTransitionManager.cs @@ -1,6 +1,3 @@ -#nullable enable -using Terminal.Gui.Drivers; - namespace Terminal.Gui.App; /// @@ -12,10 +9,11 @@ public class ToplevelTransitionManager : IToplevelTransitionManager private View? _lastTop; + /// /// - public void RaiseReadyEventIfNeeded () + public void RaiseReadyEventIfNeeded (IApplication? app) { - Toplevel? top = Application.Top; + Toplevel? top = app?.Current; if (top != null && !_readiedTopLevels.Contains (top)) { @@ -27,16 +25,17 @@ public class ToplevelTransitionManager : IToplevelTransitionManager } } + /// /// - public void HandleTopMaybeChanging () + public void HandleTopMaybeChanging (IApplication? app) { - Toplevel? newTop = Application.Top; + Toplevel? newTop = app?.Current; if (_lastTop != null && _lastTop != newTop && newTop != null) { newTop.SetNeedsDraw (); } - _lastTop = Application.Top; + _lastTop = app?.Current; } } diff --git a/Terminal.Gui/Configuration/AppSettingsScope.cs b/Terminal.Gui/Configuration/AppSettingsScope.cs index 35594cacb..66c6af7f0 100644 --- a/Terminal.Gui/Configuration/AppSettingsScope.cs +++ b/Terminal.Gui/Configuration/AppSettingsScope.cs @@ -1,5 +1,4 @@ -#nullable enable -using System.Text.Json.Serialization; +using System.Text.Json.Serialization; namespace Terminal.Gui.Configuration; diff --git a/Terminal.Gui/Configuration/AttributeJsonConverter.cs b/Terminal.Gui/Configuration/AttributeJsonConverter.cs index 34ee281c5..2a00b556f 100644 --- a/Terminal.Gui/Configuration/AttributeJsonConverter.cs +++ b/Terminal.Gui/Configuration/AttributeJsonConverter.cs @@ -9,7 +9,7 @@ namespace Terminal.Gui.Configuration; internal class AttributeJsonConverter : JsonConverter { - private static AttributeJsonConverter _instance; + private static AttributeJsonConverter? _instance; /// public static AttributeJsonConverter Instance @@ -63,7 +63,7 @@ internal class AttributeJsonConverter : JsonConverter throw new JsonException ($"{propertyName}: Unexpected token when parsing Attribute: {reader.TokenType}."); } - propertyName = reader.GetString (); + propertyName = reader.GetString ()!; reader.Read (); var property = $"\"{reader.GetString ()}\""; diff --git a/Terminal.Gui/Configuration/ColorJsonConverter.cs b/Terminal.Gui/Configuration/ColorJsonConverter.cs index 70d6ca7e7..8959a9a10 100644 --- a/Terminal.Gui/Configuration/ColorJsonConverter.cs +++ b/Terminal.Gui/Configuration/ColorJsonConverter.cs @@ -15,7 +15,7 @@ namespace Terminal.Gui.Configuration; /// internal class ColorJsonConverter : JsonConverter { - private static ColorJsonConverter _instance; + private static ColorJsonConverter? _instance; /// Singleton public static ColorJsonConverter Instance diff --git a/Terminal.Gui/Configuration/ConcurrentDictionaryJsonConverter.cs b/Terminal.Gui/Configuration/ConcurrentDictionaryJsonConverter.cs index a33f9181a..a5d186184 100644 --- a/Terminal.Gui/Configuration/ConcurrentDictionaryJsonConverter.cs +++ b/Terminal.Gui/Configuration/ConcurrentDictionaryJsonConverter.cs @@ -1,3 +1,4 @@ +#nullable disable using System.Collections.Concurrent; using System.Diagnostics.CodeAnalysis; using System.Text.Json; diff --git a/Terminal.Gui/Configuration/ConfigLocations.cs b/Terminal.Gui/Configuration/ConfigLocations.cs index 8f348fa8c..c41aacde9 100644 --- a/Terminal.Gui/Configuration/ConfigLocations.cs +++ b/Terminal.Gui/Configuration/ConfigLocations.cs @@ -1,5 +1,4 @@ -#nullable enable -namespace Terminal.Gui.Configuration; +namespace Terminal.Gui.Configuration; /// /// Describes the location of the configuration settings. The constants can be combined (bitwise) to specify multiple diff --git a/Terminal.Gui/Configuration/ConfigProperty.cs b/Terminal.Gui/Configuration/ConfigProperty.cs index 0442a3b6f..0a5d7dc8f 100644 --- a/Terminal.Gui/Configuration/ConfigProperty.cs +++ b/Terminal.Gui/Configuration/ConfigProperty.cs @@ -1,5 +1,4 @@ -#nullable enable -using System.Collections.Concurrent; +using System.Collections.Concurrent; using System.Collections.Immutable; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; diff --git a/Terminal.Gui/Configuration/ConfigurationManager.cs b/Terminal.Gui/Configuration/ConfigurationManager.cs index 6f0364ad5..b8d3cdf5d 100644 --- a/Terminal.Gui/Configuration/ConfigurationManager.cs +++ b/Terminal.Gui/Configuration/ConfigurationManager.cs @@ -1,6 +1,4 @@ -#nullable enable - -using System.Collections.Frozen; +using System.Collections.Frozen; using System.Collections.Immutable; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; diff --git a/Terminal.Gui/Configuration/ConfigurationManagerEventArgs.cs b/Terminal.Gui/Configuration/ConfigurationManagerEventArgs.cs index 24a325ab7..ea03daff1 100644 --- a/Terminal.Gui/Configuration/ConfigurationManagerEventArgs.cs +++ b/Terminal.Gui/Configuration/ConfigurationManagerEventArgs.cs @@ -1,6 +1,4 @@ -#nullable enable - -namespace Terminal.Gui.Configuration; +namespace Terminal.Gui.Configuration; /// Event arguments for the events. public class ConfigurationManagerEventArgs : EventArgs diff --git a/Terminal.Gui/Configuration/ConfigurationManagerNotEnabledException.cs b/Terminal.Gui/Configuration/ConfigurationManagerNotEnabledException.cs index 45206c876..f9fc6b62e 100644 --- a/Terminal.Gui/Configuration/ConfigurationManagerNotEnabledException.cs +++ b/Terminal.Gui/Configuration/ConfigurationManagerNotEnabledException.cs @@ -1,5 +1,4 @@ -#nullable enable -namespace Terminal.Gui.Configuration; +namespace Terminal.Gui.Configuration; /// /// The exception that is thrown when a API is called but the configuration manager is not enabled. diff --git a/Terminal.Gui/Configuration/ConfigurationPropertyAttribute.cs b/Terminal.Gui/Configuration/ConfigurationPropertyAttribute.cs index 2f1218aa9..3911e9f88 100644 --- a/Terminal.Gui/Configuration/ConfigurationPropertyAttribute.cs +++ b/Terminal.Gui/Configuration/ConfigurationPropertyAttribute.cs @@ -1,6 +1,4 @@ -#nullable enable - -namespace Terminal.Gui.Configuration; +namespace Terminal.Gui.Configuration; /// An attribute indicating a property is managed by . /// diff --git a/Terminal.Gui/Configuration/DeepCloner.cs b/Terminal.Gui/Configuration/DeepCloner.cs index 0d918625c..3a6caec52 100644 --- a/Terminal.Gui/Configuration/DeepCloner.cs +++ b/Terminal.Gui/Configuration/DeepCloner.cs @@ -1,4 +1,4 @@ -#nullable enable + using System.Collections; using System.Collections.Concurrent; diff --git a/Terminal.Gui/Configuration/DictionaryJsonConverter.cs b/Terminal.Gui/Configuration/DictionaryJsonConverter.cs index bfd940d33..2cdbbfd48 100644 --- a/Terminal.Gui/Configuration/DictionaryJsonConverter.cs +++ b/Terminal.Gui/Configuration/DictionaryJsonConverter.cs @@ -1,4 +1,5 @@ -using System.Diagnostics.CodeAnalysis; +#nullable disable +using System.Diagnostics.CodeAnalysis; using System.Text.Json; using System.Text.Json.Serialization; diff --git a/Terminal.Gui/Configuration/KeyCodeJsonConverter.cs b/Terminal.Gui/Configuration/KeyCodeJsonConverter.cs index 04d8d9765..d69ea9479 100644 --- a/Terminal.Gui/Configuration/KeyCodeJsonConverter.cs +++ b/Terminal.Gui/Configuration/KeyCodeJsonConverter.cs @@ -1,4 +1,5 @@ -using System.Text.Json; +#nullable disable +using System.Text.Json; using System.Text.Json.Serialization; namespace Terminal.Gui.Configuration; diff --git a/Terminal.Gui/Configuration/KeyJsonConverter.cs b/Terminal.Gui/Configuration/KeyJsonConverter.cs index 01413c432..1e723c8b7 100644 --- a/Terminal.Gui/Configuration/KeyJsonConverter.cs +++ b/Terminal.Gui/Configuration/KeyJsonConverter.cs @@ -9,7 +9,7 @@ public class KeyJsonConverter : JsonConverter /// public override Key Read (ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { - return Key.TryParse (reader.GetString (), out Key key) ? key : Key.Empty; + return Key.TryParse (reader.GetString ()!, out Key key) ? key : Key.Empty; } /// diff --git a/Terminal.Gui/Configuration/RuneJsonConverter.cs b/Terminal.Gui/Configuration/RuneJsonConverter.cs index 8fc7f9f7b..48ec57518 100644 --- a/Terminal.Gui/Configuration/RuneJsonConverter.cs +++ b/Terminal.Gui/Configuration/RuneJsonConverter.cs @@ -26,7 +26,7 @@ internal class RuneJsonConverter : JsonConverter { case JsonTokenType.String: { - string value = reader.GetString (); + string? value = reader.GetString (); int first = RuneExtensions.MaxUnicodeCodePoint + 1; int second = RuneExtensions.MaxUnicodeCodePoint + 1; diff --git a/Terminal.Gui/Configuration/SchemeJsonConverter.cs b/Terminal.Gui/Configuration/SchemeJsonConverter.cs index cabeefacf..fe363dc54 100644 --- a/Terminal.Gui/Configuration/SchemeJsonConverter.cs +++ b/Terminal.Gui/Configuration/SchemeJsonConverter.cs @@ -1,5 +1,4 @@ -#nullable enable -using System.Diagnostics.CodeAnalysis; +using System.Diagnostics.CodeAnalysis; using System.Text.Json; using System.Text.Json.Serialization; diff --git a/Terminal.Gui/Configuration/SchemeManager.cs b/Terminal.Gui/Configuration/SchemeManager.cs index 4fa1fd809..0023a0824 100644 --- a/Terminal.Gui/Configuration/SchemeManager.cs +++ b/Terminal.Gui/Configuration/SchemeManager.cs @@ -1,5 +1,4 @@ -#nullable enable -using System.Collections.Immutable; +using System.Collections.Immutable; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Text.Json.Serialization; diff --git a/Terminal.Gui/Configuration/Scope.cs b/Terminal.Gui/Configuration/Scope.cs index 88d637264..a3fe4f069 100644 --- a/Terminal.Gui/Configuration/Scope.cs +++ b/Terminal.Gui/Configuration/Scope.cs @@ -1,5 +1,4 @@ -#nullable enable -using System.Collections.Concurrent; +using System.Collections.Concurrent; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; diff --git a/Terminal.Gui/Configuration/ScopeJsonConverter.cs b/Terminal.Gui/Configuration/ScopeJsonConverter.cs index 034e904ae..94ed04e8f 100644 --- a/Terminal.Gui/Configuration/ScopeJsonConverter.cs +++ b/Terminal.Gui/Configuration/ScopeJsonConverter.cs @@ -1,5 +1,4 @@ -#nullable enable -using System.Diagnostics.CodeAnalysis; +using System.Diagnostics.CodeAnalysis; using System.Reflection; using System.Text.Json; using System.Text.Json.Serialization; diff --git a/Terminal.Gui/Configuration/SettingsScope.cs b/Terminal.Gui/Configuration/SettingsScope.cs index 5feeb1131..de9dbdeb8 100644 --- a/Terminal.Gui/Configuration/SettingsScope.cs +++ b/Terminal.Gui/Configuration/SettingsScope.cs @@ -1,5 +1,4 @@ -#nullable enable -using System.Text.Json.Serialization; +using System.Text.Json.Serialization; namespace Terminal.Gui.Configuration; diff --git a/Terminal.Gui/Configuration/SourcesManager.cs b/Terminal.Gui/Configuration/SourcesManager.cs index 729086541..541d452a2 100644 --- a/Terminal.Gui/Configuration/SourcesManager.cs +++ b/Terminal.Gui/Configuration/SourcesManager.cs @@ -1,5 +1,4 @@ -#nullable enable -using System.Diagnostics; +using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Reflection; using System.Text.Json; diff --git a/Terminal.Gui/Configuration/ThemeManager.cs b/Terminal.Gui/Configuration/ThemeManager.cs index b184ba9ba..a234ac869 100644 --- a/Terminal.Gui/Configuration/ThemeManager.cs +++ b/Terminal.Gui/Configuration/ThemeManager.cs @@ -1,5 +1,4 @@ -#nullable enable -using System.Collections.Concurrent; +using System.Collections.Concurrent; using System.Collections.Immutable; using System.Diagnostics.CodeAnalysis; using System.Text.Json.Serialization; diff --git a/Terminal.Gui/Configuration/ThemeScope.cs b/Terminal.Gui/Configuration/ThemeScope.cs index 541cb80f6..29bef03d8 100644 --- a/Terminal.Gui/Configuration/ThemeScope.cs +++ b/Terminal.Gui/Configuration/ThemeScope.cs @@ -1,5 +1,4 @@ -#nullable enable -using System.Text.Json.Serialization; +using System.Text.Json.Serialization; namespace Terminal.Gui.Configuration; diff --git a/Terminal.Gui/Drawing/Attribute.cs b/Terminal.Gui/Drawing/Attribute.cs index bc7005c41..0b794f20c 100644 --- a/Terminal.Gui/Drawing/Attribute.cs +++ b/Terminal.Gui/Drawing/Attribute.cs @@ -1,5 +1,4 @@ -#nullable enable -using System.Numerics; +using System.Numerics; using System.Text.Json.Serialization; namespace Terminal.Gui.Drawing; diff --git a/Terminal.Gui/Drawing/Cell.cs b/Terminal.Gui/Drawing/Cell.cs index e72a7837e..2fa98bef1 100644 --- a/Terminal.Gui/Drawing/Cell.cs +++ b/Terminal.Gui/Drawing/Cell.cs @@ -1,5 +1,4 @@ -#nullable enable - + namespace Terminal.Gui.Drawing; diff --git a/Terminal.Gui/Drawing/Color/AnsiColorCode.cs b/Terminal.Gui/Drawing/Color/AnsiColorCode.cs index 56bc857a8..28d1fafb7 100644 --- a/Terminal.Gui/Drawing/Color/AnsiColorCode.cs +++ b/Terminal.Gui/Drawing/Color/AnsiColorCode.cs @@ -1,3 +1,4 @@ +// ReSharper disable InconsistentNaming namespace Terminal.Gui.Drawing; /// diff --git a/Terminal.Gui/Drawing/Color/AnsiColorNameResolver.cs b/Terminal.Gui/Drawing/Color/AnsiColorNameResolver.cs index aae4a6da5..5a0ebc827 100644 --- a/Terminal.Gui/Drawing/Color/AnsiColorNameResolver.cs +++ b/Terminal.Gui/Drawing/Color/AnsiColorNameResolver.cs @@ -1,5 +1,3 @@ -#nullable enable - using System.Collections.Immutable; using System.Diagnostics.CodeAnalysis; diff --git a/Terminal.Gui/Drawing/Color/Color.ColorExtensions.cs b/Terminal.Gui/Drawing/Color/Color.ColorExtensions.cs index 84e5f089a..a0b23b545 100644 --- a/Terminal.Gui/Drawing/Color/Color.ColorExtensions.cs +++ b/Terminal.Gui/Drawing/Color/Color.ColorExtensions.cs @@ -1,4 +1,3 @@ -#nullable enable using System.Collections.Frozen; using ColorHelper; diff --git a/Terminal.Gui/Drawing/Color/Color.ColorParseException.cs b/Terminal.Gui/Drawing/Color/Color.ColorParseException.cs index 97595db04..ac1da5d5f 100644 --- a/Terminal.Gui/Drawing/Color/Color.ColorParseException.cs +++ b/Terminal.Gui/Drawing/Color/Color.ColorParseException.cs @@ -1,4 +1,3 @@ -#nullable enable using System.Diagnostics.CodeAnalysis; namespace Terminal.Gui.Drawing; diff --git a/Terminal.Gui/Drawing/Color/Color.Formatting.cs b/Terminal.Gui/Drawing/Color/Color.Formatting.cs index 15e0dccb9..89082867b 100644 --- a/Terminal.Gui/Drawing/Color/Color.Formatting.cs +++ b/Terminal.Gui/Drawing/Color/Color.Formatting.cs @@ -1,4 +1,3 @@ -#nullable enable using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.Runtime.CompilerServices; diff --git a/Terminal.Gui/Drawing/Color/Color.Operators.cs b/Terminal.Gui/Drawing/Color/Color.Operators.cs index 831f32bab..b8d33bd4a 100644 --- a/Terminal.Gui/Drawing/Color/Color.Operators.cs +++ b/Terminal.Gui/Drawing/Color/Color.Operators.cs @@ -1,4 +1,3 @@ -#nullable enable using System.Diagnostics.Contracts; using System.Numerics; diff --git a/Terminal.Gui/Drawing/Color/Color.cs b/Terminal.Gui/Drawing/Color/Color.cs index 995249c13..e83a05a73 100644 --- a/Terminal.Gui/Drawing/Color/Color.cs +++ b/Terminal.Gui/Drawing/Color/Color.cs @@ -1,4 +1,3 @@ -#nullable enable using System.Collections.Frozen; using System.Globalization; using System.Numerics; diff --git a/Terminal.Gui/Drawing/Color/ColorModel.cs b/Terminal.Gui/Drawing/Color/ColorModel.cs index 6af865a9c..158c03236 100644 --- a/Terminal.Gui/Drawing/Color/ColorModel.cs +++ b/Terminal.Gui/Drawing/Color/ColorModel.cs @@ -1,5 +1,3 @@ -#nullable enable - namespace Terminal.Gui.Drawing; /// diff --git a/Terminal.Gui/Drawing/Color/ColorStrings.cs b/Terminal.Gui/Drawing/Color/ColorStrings.cs index 705ea13e4..8f70c6a9e 100644 --- a/Terminal.Gui/Drawing/Color/ColorStrings.cs +++ b/Terminal.Gui/Drawing/Color/ColorStrings.cs @@ -1,4 +1,3 @@ -#nullable enable using System.Globalization; namespace Terminal.Gui.Drawing; diff --git a/Terminal.Gui/Drawing/Color/IColorNameResolver.cs b/Terminal.Gui/Drawing/Color/IColorNameResolver.cs index 36881adb3..14e44718f 100644 --- a/Terminal.Gui/Drawing/Color/IColorNameResolver.cs +++ b/Terminal.Gui/Drawing/Color/IColorNameResolver.cs @@ -1,5 +1,3 @@ -#nullable enable - using System.Diagnostics.CodeAnalysis; namespace Terminal.Gui.Drawing; diff --git a/Terminal.Gui/Drawing/Color/ICustomColorFormatter.cs b/Terminal.Gui/Drawing/Color/ICustomColorFormatter.cs index 425a02441..3bcd14919 100644 --- a/Terminal.Gui/Drawing/Color/ICustomColorFormatter.cs +++ b/Terminal.Gui/Drawing/Color/ICustomColorFormatter.cs @@ -1,4 +1,3 @@ -#nullable enable namespace Terminal.Gui.Drawing; /// An interface to support custom formatting and parsing of values. diff --git a/Terminal.Gui/Drawing/Color/MultiStandardColorNameResolver.cs b/Terminal.Gui/Drawing/Color/MultiStandardColorNameResolver.cs index d409950eb..36ed50910 100644 --- a/Terminal.Gui/Drawing/Color/MultiStandardColorNameResolver.cs +++ b/Terminal.Gui/Drawing/Color/MultiStandardColorNameResolver.cs @@ -1,5 +1,3 @@ -#nullable enable - using System.Collections.Frozen; using System.Collections.Immutable; using System.Diagnostics.CodeAnalysis; diff --git a/Terminal.Gui/Drawing/Color/StandardColors.cs b/Terminal.Gui/Drawing/Color/StandardColors.cs index 05adfdb30..7e5b83831 100644 --- a/Terminal.Gui/Drawing/Color/StandardColors.cs +++ b/Terminal.Gui/Drawing/Color/StandardColors.cs @@ -1,4 +1,3 @@ -#nullable enable using System.Collections.Frozen; using System.Collections.Immutable; using System.Diagnostics.CodeAnalysis; diff --git a/Terminal.Gui/Drawing/Color/StandardColorsNameResolver.cs b/Terminal.Gui/Drawing/Color/StandardColorsNameResolver.cs index daae80b9e..0c8a4257a 100644 --- a/Terminal.Gui/Drawing/Color/StandardColorsNameResolver.cs +++ b/Terminal.Gui/Drawing/Color/StandardColorsNameResolver.cs @@ -1,5 +1,3 @@ -#nullable enable - using System.Diagnostics.CodeAnalysis; namespace Terminal.Gui.Drawing; @@ -17,4 +15,4 @@ public class StandardColorsNameResolver : IColorNameResolver /// public bool TryNameColor (Color color, [NotNullWhen (true)] out string? name) => StandardColors.TryNameColor (color, out name); -} \ No newline at end of file +} diff --git a/Terminal.Gui/Drawing/Glyphs.cs b/Terminal.Gui/Drawing/Glyphs.cs index 28eb3a546..71336009d 100644 --- a/Terminal.Gui/Drawing/Glyphs.cs +++ b/Terminal.Gui/Drawing/Glyphs.cs @@ -1,5 +1,4 @@ -#nullable enable - + namespace Terminal.Gui.Drawing; /// Defines the standard set of glyphs used to draw checkboxes, lines, borders, etc... diff --git a/Terminal.Gui/Drawing/LineCanvas/IntersectionDefinition.cs b/Terminal.Gui/Drawing/LineCanvas/IntersectionDefinition.cs index a03f8bca8..9f2c77df7 100644 --- a/Terminal.Gui/Drawing/LineCanvas/IntersectionDefinition.cs +++ b/Terminal.Gui/Drawing/LineCanvas/IntersectionDefinition.cs @@ -17,4 +17,4 @@ internal class IntersectionDefinition /// Defines how position relates to . internal IntersectionType Type { get; } -} \ No newline at end of file +} diff --git a/Terminal.Gui/Drawing/LineCanvas/IntersectionRuneType.cs b/Terminal.Gui/Drawing/LineCanvas/IntersectionRuneType.cs index b32310fa7..c3a0043f7 100644 --- a/Terminal.Gui/Drawing/LineCanvas/IntersectionRuneType.cs +++ b/Terminal.Gui/Drawing/LineCanvas/IntersectionRuneType.cs @@ -16,4 +16,4 @@ internal enum IntersectionRuneType Cross, HLine, VLine -} \ No newline at end of file +} diff --git a/Terminal.Gui/Drawing/LineCanvas/IntersectionType.cs b/Terminal.Gui/Drawing/LineCanvas/IntersectionType.cs index 87a051e55..e0cecf815 100644 --- a/Terminal.Gui/Drawing/LineCanvas/IntersectionType.cs +++ b/Terminal.Gui/Drawing/LineCanvas/IntersectionType.cs @@ -25,4 +25,4 @@ internal enum IntersectionType /// A line exists at this point who has 0 length Dot -} \ No newline at end of file +} diff --git a/Terminal.Gui/Drawing/LineCanvas/LineCanvas.cs b/Terminal.Gui/Drawing/LineCanvas/LineCanvas.cs index 637b61861..c426b6896 100644 --- a/Terminal.Gui/Drawing/LineCanvas/LineCanvas.cs +++ b/Terminal.Gui/Drawing/LineCanvas/LineCanvas.cs @@ -1,4 +1,3 @@ -#nullable enable using System.Runtime.InteropServices; @@ -181,7 +180,7 @@ public class LineCanvas : IDisposable } // Safe as long as the list is not modified while the span is in use. ReadOnlySpan intersects = CollectionsMarshal.AsSpan(intersectionsBufferList); - Cell? cell = GetCellForIntersects (Application.Driver, intersects); + Cell? cell = GetCellForIntersects (intersects); // TODO: Can we skip the whole nested looping if _exclusionRegion is null? if (cell is { } && _exclusionRegion?.Contains (x, y) is null or false) { @@ -223,7 +222,7 @@ public class LineCanvas : IDisposable .Where (i => i is not null) .ToArray (); - Rune? rune = GetRuneForIntersects (Application.Driver, intersects); + Rune? rune = GetRuneForIntersects (intersects); if (rune is { } && _exclusionRegion?.Contains (x, y) is null or false) { @@ -402,7 +401,7 @@ public class LineCanvas : IDisposable // TODO: Add other resolvers }; - private Cell? GetCellForIntersects (IDriver? driver, ReadOnlySpan intersects) + private Cell? GetCellForIntersects (ReadOnlySpan intersects) { if (intersects.IsEmpty) { @@ -410,7 +409,7 @@ public class LineCanvas : IDisposable } var cell = new Cell (); - Rune? rune = GetRuneForIntersects (driver, intersects); + Rune? rune = GetRuneForIntersects (intersects); if (rune.HasValue) { @@ -422,7 +421,7 @@ public class LineCanvas : IDisposable return cell; } - private Rune? GetRuneForIntersects (IDriver? driver, ReadOnlySpan intersects) + private Rune? GetRuneForIntersects (ReadOnlySpan intersects) { if (intersects.IsEmpty) { @@ -432,7 +431,7 @@ public class LineCanvas : IDisposable IntersectionRuneType runeType = GetRuneTypeForIntersects (intersects); if (_runeResolvers.TryGetValue (runeType, out IntersectionRuneResolver? resolver)) { - return resolver.GetRuneForIntersects (driver, intersects); + return resolver.GetRuneForIntersects (intersects); } // TODO: Remove these once we have all of the below ported to IntersectionRuneResolvers @@ -769,7 +768,7 @@ public class LineCanvas : IDisposable internal Rune _thickV; protected IntersectionRuneResolver () { SetGlyphs (); } - public Rune? GetRuneForIntersects (IDriver? driver, ReadOnlySpan intersects) + public Rune? GetRuneForIntersects (ReadOnlySpan intersects) { // Note that there aren't any glyphs for intersections of double lines with heavy lines diff --git a/Terminal.Gui/Drawing/LineCanvas/LineStyle.cs b/Terminal.Gui/Drawing/LineCanvas/LineStyle.cs index 9c8f234fe..9b93708db 100644 --- a/Terminal.Gui/Drawing/LineCanvas/LineStyle.cs +++ b/Terminal.Gui/Drawing/LineCanvas/LineStyle.cs @@ -1,4 +1,3 @@ -#nullable enable using System.Text.Json.Serialization; namespace Terminal.Gui.Drawing; diff --git a/Terminal.Gui/Drawing/LineCanvas/StraightLine.cs b/Terminal.Gui/Drawing/LineCanvas/StraightLine.cs index 5e64c0b7a..b4f86665d 100644 --- a/Terminal.Gui/Drawing/LineCanvas/StraightLine.cs +++ b/Terminal.Gui/Drawing/LineCanvas/StraightLine.cs @@ -1,6 +1,5 @@  namespace Terminal.Gui.Drawing; -#nullable enable // TODO: Add events that notify when StraightLine changes to enable dynamic layout /// A line between two points on a horizontal or vertical and a given style/color. diff --git a/Terminal.Gui/Drawing/LineCanvas/StraightLineExtensions.cs b/Terminal.Gui/Drawing/LineCanvas/StraightLineExtensions.cs index 1cff548ae..f8089952f 100644 --- a/Terminal.Gui/Drawing/LineCanvas/StraightLineExtensions.cs +++ b/Terminal.Gui/Drawing/LineCanvas/StraightLineExtensions.cs @@ -1,4 +1,4 @@ - + namespace Terminal.Gui.Drawing; /// Extension methods for (including collections). @@ -187,7 +187,7 @@ public static class StraightLineExtensions { if (length == 0) { - throw new ArgumentException ("0 length lines are not supported", nameof (length)); + throw new ArgumentException (@"0 length lines are not supported", nameof (length)); } int sub = length > 0 ? 1 : -1; @@ -220,7 +220,7 @@ public static class StraightLineExtensions { if (length == 0) { - throw new ArgumentException ("0 length lines are not supported", nameof (length)); + throw new ArgumentException (@"0 length lines are not supported", nameof (length)); } int sub = length > 0 ? 1 : -1; diff --git a/Terminal.Gui/Drawing/Quant/PopularityPaletteWithThreshold.cs b/Terminal.Gui/Drawing/Quant/PopularityPaletteWithThreshold.cs index 0e7f53596..c98050941 100644 --- a/Terminal.Gui/Drawing/Quant/PopularityPaletteWithThreshold.cs +++ b/Terminal.Gui/Drawing/Quant/PopularityPaletteWithThreshold.cs @@ -1,8 +1,9 @@ - +namespace Terminal.Gui.Drawing; + /// /// Simple fast palette building algorithm which uses the frequency that a color is seen -/// to determine whether it will appear in the final palette. Includes a threshold where -/// by colors will be considered 'the same'. This reduces the chance of under represented +/// to determine whether it will appear in the final palette. Includes a threshold whereby +/// colors will be considered 'the same'. This reduces the chance of underrepresented /// colors being missed completely. /// public class PopularityPaletteWithThreshold : IPaletteBuilder @@ -22,11 +23,11 @@ public class PopularityPaletteWithThreshold : IPaletteBuilder } /// - public List BuildPalette (List colors, int maxColors) + public List BuildPalette (List? colors, int maxColors) { - if (colors == null || colors.Count == 0 || maxColors <= 0) + if (colors is null || colors.Count == 0 || maxColors <= 0) { - return new (); + return []; } // Step 1: Build the histogram of colors (count occurrences) @@ -34,14 +35,10 @@ public class PopularityPaletteWithThreshold : IPaletteBuilder foreach (Color color in colors) { - if (colorHistogram.ContainsKey (color)) + if (!colorHistogram.TryAdd (color, 1)) { colorHistogram [color]++; } - else - { - colorHistogram [color] = 1; - } } // If we already have fewer or equal colors than the limit, no need to merge diff --git a/Terminal.Gui/Drawing/Region.cs b/Terminal.Gui/Drawing/Region.cs index f3abb8e07..597801a6a 100644 --- a/Terminal.Gui/Drawing/Region.cs +++ b/Terminal.Gui/Drawing/Region.cs @@ -1,5 +1,4 @@ -#nullable enable - + namespace Terminal.Gui.Drawing; @@ -51,10 +50,10 @@ public class Region private readonly List _rectangles = []; // Add a single reusable list for temp operations - private readonly List _tempRectangles = new(); + private readonly List _tempRectangles = new (); // Object used for synchronization - private readonly object _syncLock = new object(); + private readonly object _syncLock = new object (); /// /// Initializes a new instance of the class. @@ -121,12 +120,12 @@ public class Region { lock (_syncLock) { - CombineInternal(region, operation); + CombineInternal (region, operation); } } // Private method to implement the combine logic within a lock - private void CombineInternal(Region? region, RegionOp operation) + private void CombineInternal (Region? region, RegionOp operation) { if (region is null || region._rectangles.Count == 0) { @@ -189,36 +188,36 @@ public class Region case RegionOp.Union: // Avoid collection initialization with spread operator - _tempRectangles.Clear(); - _tempRectangles.AddRange(_rectangles); + _tempRectangles.Clear (); + _tempRectangles.AddRange (_rectangles); if (region != null) { // Get the region's rectangles safely lock (region._syncLock) { - _tempRectangles.AddRange(region._rectangles); + _tempRectangles.AddRange (region._rectangles); } } - List mergedUnion = MergeRectangles(_tempRectangles, false); - _rectangles.Clear(); - _rectangles.AddRange(mergedUnion); + List mergedUnion = MergeRectangles (_tempRectangles, false); + _rectangles.Clear (); + _rectangles.AddRange (mergedUnion); break; case RegionOp.MinimalUnion: // Avoid collection initialization with spread operator - _tempRectangles.Clear(); - _tempRectangles.AddRange(_rectangles); + _tempRectangles.Clear (); + _tempRectangles.AddRange (_rectangles); if (region != null) { // Get the region's rectangles safely lock (region._syncLock) { - _tempRectangles.AddRange(region._rectangles); + _tempRectangles.AddRange (region._rectangles); } } - List mergedMinimalUnion = MergeRectangles(_tempRectangles, true); - _rectangles.Clear(); - _rectangles.AddRange(mergedMinimalUnion); + List mergedMinimalUnion = MergeRectangles (_tempRectangles, true); + _rectangles.Clear (); + _rectangles.AddRange (mergedMinimalUnion); break; case RegionOp.XOR: @@ -588,17 +587,26 @@ public class Region { // 1. Sort by X int cmp = a.x.CompareTo (b.x); - if (cmp != 0) return cmp; + if (cmp != 0) + { + return cmp; + } // 2. Sort End events before Start events bool aIsEnd = !a.isStart; bool bIsEnd = !b.isStart; cmp = aIsEnd.CompareTo (bIsEnd); // True (End) comes after False (Start) - if (cmp != 0) return -cmp; // Reverse: End (true) should come before Start (false) + if (cmp != 0) + { + return -cmp; // Reverse: End (true) should come before Start (false) + } // 3. Tie-breaker: Sort by yTop cmp = a.yTop.CompareTo (b.yTop); - if (cmp != 0) return cmp; + if (cmp != 0) + { + return cmp; + } // 4. Final Tie-breaker: Sort by yBottom return a.yBottom.CompareTo (b.yBottom); @@ -901,13 +909,16 @@ public class Region /// /// Fills the interior of all rectangles in the region with the specified attribute and fill rune. /// + /// /// The attribute (color/style) to use. /// /// The rune to fill the interior of the rectangles with. If space will be /// used. /// - public void FillRectangles (Attribute attribute, Rune? fillRune = null) + public void FillRectangles (IDriver? driver, Attribute? attribute, Rune? fillRune = null) { + ArgumentNullException.ThrowIfNull (driver); + if (_rectangles.Count == 0) { return; @@ -920,14 +931,14 @@ public class Region continue; } - Application.Driver?.SetAttribute (attribute); + driver?.SetAttribute (attribute!.Value); for (int y = rect.Top; y < rect.Bottom; y++) { for (int x = rect.Left; x < rect.Right; x++) { - Application.Driver?.Move (x, y); - Application.Driver?.AddRune (fillRune ?? (Rune)' '); + driver?.Move (x, y); + driver?.AddRune (fillRune ?? (Rune)' '); } } } @@ -1046,7 +1057,7 @@ public class Region if (bounds.Width > 1000 || bounds.Height > 1000) { // Fall back to drawing each rectangle's boundary - DrawBoundaries(lineCanvas, style, attribute); + DrawBoundaries (lineCanvas, style, attribute); return; } diff --git a/Terminal.Gui/Drawing/RegionOp.cs b/Terminal.Gui/Drawing/RegionOp.cs index e40de1663..0b4689a37 100644 --- a/Terminal.Gui/Drawing/RegionOp.cs +++ b/Terminal.Gui/Drawing/RegionOp.cs @@ -1,5 +1,4 @@ -#nullable enable - + namespace Terminal.Gui.Drawing; /// diff --git a/Terminal.Gui/Drawing/Ruler.cs b/Terminal.Gui/Drawing/Ruler.cs index 89ef6b6d1..4c457976f 100644 --- a/Terminal.Gui/Drawing/Ruler.cs +++ b/Terminal.Gui/Drawing/Ruler.cs @@ -1,5 +1,4 @@ -#nullable enable - + namespace Terminal.Gui.Drawing; /// Draws a ruler on the screen. @@ -21,11 +20,13 @@ internal class Ruler private string _vTemplate { get; } = "-123456789"; /// Draws the . + /// Optional Driver. If not provided, driver will be used. /// The location to start drawing the ruler, in screen-relative coordinates. /// The start value of the ruler. - /// Optional Driver. If not provided, driver will be used. - public void Draw (Point location, int start = 0, IDriver? driver = null) + public void Draw (IDriver? driver, Point location, int start = 0) { + ArgumentNullException.ThrowIfNull (driver); + if (start < 0) { throw new ArgumentException ("start must be greater than or equal to 0"); @@ -36,8 +37,6 @@ internal class Ruler return; } - driver ??= driver; - if (Orientation == Orientation.Horizontal) { string hrule = diff --git a/Terminal.Gui/Drawing/Scheme.cs b/Terminal.Gui/Drawing/Scheme.cs index ff0933aac..9bd612537 100644 --- a/Terminal.Gui/Drawing/Scheme.cs +++ b/Terminal.Gui/Drawing/Scheme.cs @@ -1,4 +1,3 @@ -#nullable enable using System.Collections.Immutable; using System.Numerics; using System.Text.Json.Serialization; diff --git a/Terminal.Gui/Drawing/Sixel/SixelSupportDetector.cs b/Terminal.Gui/Drawing/Sixel/SixelSupportDetector.cs index 01df783a4..a9e1ae8aa 100644 --- a/Terminal.Gui/Drawing/Sixel/SixelSupportDetector.cs +++ b/Terminal.Gui/Drawing/Sixel/SixelSupportDetector.cs @@ -6,8 +6,21 @@ namespace Terminal.Gui.Drawing; /// Uses Ansi escape sequences to detect whether sixel is supported /// by the terminal. /// -public class SixelSupportDetector +public class SixelSupportDetector () { + private readonly IDriver? _driver; + + /// + /// Creates a new instance of the class. + /// + /// + public SixelSupportDetector (IDriver? driver) : this () + { + ArgumentNullException.ThrowIfNull (driver); + + _driver = driver; + } + /// /// Sends Ansi escape sequences to the console to determine whether /// sixel is supported (and @@ -127,17 +140,17 @@ public class SixelSupportDetector () => resultCallback (result)); } - private static void QueueRequest (AnsiEscapeSequence req, Action responseCallback, Action abandoned) + private void QueueRequest (AnsiEscapeSequence req, Action responseCallback, Action abandoned) { var newRequest = new AnsiEscapeSequenceRequest { Request = req.Request, Terminator = req.Terminator, - ResponseReceived = responseCallback, + ResponseReceived = responseCallback!, Abandoned = abandoned }; - Application.Driver?.QueueAnsiRequest (newRequest); + _driver?.QueueAnsiRequest (newRequest); } private static bool ResponseIndicatesSupport (string response) { return response.Split (';').Contains ("4"); } @@ -145,14 +158,12 @@ public class SixelSupportDetector private static bool IsVirtualTerminal () { return !string.IsNullOrWhiteSpace (Environment.GetEnvironmentVariable ("WT_SESSION")); - - ; } private static bool IsXtermWithTransparency () { // Check if running in real xterm (XTERM_VERSION is more reliable than TERM) - string xtermVersionStr = Environment.GetEnvironmentVariable ("XTERM_VERSION"); + string xtermVersionStr = Environment.GetEnvironmentVariable (@"XTERM_VERSION")!; // If XTERM_VERSION exists, we are in a real xterm if (!string.IsNullOrWhiteSpace (xtermVersionStr) && int.TryParse (xtermVersionStr, out int xtermVersion) && xtermVersion >= 370) diff --git a/Terminal.Gui/Drawing/Sixel/SixelToRender.cs b/Terminal.Gui/Drawing/Sixel/SixelToRender.cs index c66d4bdaf..89c3b26b5 100644 --- a/Terminal.Gui/Drawing/Sixel/SixelToRender.cs +++ b/Terminal.Gui/Drawing/Sixel/SixelToRender.cs @@ -10,7 +10,7 @@ public class SixelToRender /// gets or sets the encoded sixel data. Use to convert bitmaps /// into encoded sixel data. /// - public string SixelData { get; set; } + public string? SixelData { get; set; } /// /// gets or sets where to move the cursor to before outputting the . diff --git a/Terminal.Gui/Drawing/Thickness.cs b/Terminal.Gui/Drawing/Thickness.cs index c89773f1c..a15b49fd4 100644 --- a/Terminal.Gui/Drawing/Thickness.cs +++ b/Terminal.Gui/Drawing/Thickness.cs @@ -1,5 +1,4 @@ -#nullable enable -using System.Numerics; +using System.Numerics; using System.Text.Json.Serialization; namespace Terminal.Gui.Drawing; @@ -90,15 +89,15 @@ public record struct Thickness /// The diagnostics label to draw on the bottom of the . /// Optional driver. If not specified, will be used. /// The inner rectangle remaining to be drawn. - public Rectangle Draw (Rectangle rect, ViewDiagnosticFlags diagnosticFlags = ViewDiagnosticFlags.Off, string? label = null, IDriver? driver = null) + public Rectangle Draw (IDriver? driver, Rectangle rect, ViewDiagnosticFlags diagnosticFlags = ViewDiagnosticFlags.Off, string? label = null) { + ArgumentNullException.ThrowIfNull (driver); + if (rect.Size.Width < 1 || rect.Size.Height < 1) { return Rectangle.Empty; } - driver ??= Application.Driver; - var clearChar = (Rune)' '; Rune leftChar = clearChar; Rune rightChar = clearChar; @@ -165,7 +164,7 @@ public record struct Thickness if (Top > 0) { - hRuler.Draw (rect.Location, driver: driver); + hRuler.Draw (driver: driver, location: rect.Location); } //Left @@ -173,19 +172,19 @@ public record struct Thickness if (Left > 0) { - vRuler.Draw (rect.Location with { Y = rect.Y + 1 }, 1, driver); + vRuler.Draw (driver, rect.Location with { Y = rect.Y + 1 }, 1); } // Bottom if (Bottom > 0) { - hRuler.Draw (rect.Location with { Y = rect.Y + rect.Height - 1 }, driver: driver); + hRuler.Draw (driver: driver, location: rect.Location with { Y = rect.Y + rect.Height - 1 }); } // Right if (Right > 0) { - vRuler.Draw (new (rect.X + rect.Width - 1, rect.Y + 1), 1, driver); + vRuler.Draw (driver, new (rect.X + rect.Width - 1, rect.Y + 1), 1); } } @@ -205,7 +204,7 @@ public record struct Thickness if (driver?.CurrentAttribute is { }) { - tf.Draw (rect, driver!.CurrentAttribute, driver!.CurrentAttribute, rect, driver); + tf.Draw (driver, rect, driver!.CurrentAttribute, driver!.CurrentAttribute, rect); } } diff --git a/Terminal.Gui/Drawing/VisualRoleEventArgs.cs b/Terminal.Gui/Drawing/VisualRoleEventArgs.cs index 5f0389967..bdc54af8f 100644 --- a/Terminal.Gui/Drawing/VisualRoleEventArgs.cs +++ b/Terminal.Gui/Drawing/VisualRoleEventArgs.cs @@ -1,5 +1,4 @@ -#nullable enable -namespace Terminal.Gui.Drawing; +namespace Terminal.Gui.Drawing; using System; @@ -60,4 +59,4 @@ public class VisualRoleEventArgs : ResultEventArgs } } -#pragma warning restore CS1711 \ No newline at end of file +#pragma warning restore CS1711 diff --git a/Terminal.Gui/Drivers/AnsiHandling/AnsiEscapeSequence.cs b/Terminal.Gui/Drivers/AnsiHandling/AnsiEscapeSequence.cs index 5b0471776..b90bf3702 100644 --- a/Terminal.Gui/Drivers/AnsiHandling/AnsiEscapeSequence.cs +++ b/Terminal.Gui/Drivers/AnsiHandling/AnsiEscapeSequence.cs @@ -1,4 +1,3 @@ -#nullable enable namespace Terminal.Gui.Drivers; /// diff --git a/Terminal.Gui/Drivers/AnsiHandling/AnsiEscapeSequenceRequest.cs b/Terminal.Gui/Drivers/AnsiHandling/AnsiEscapeSequenceRequest.cs index de61ae920..cce053a1a 100644 --- a/Terminal.Gui/Drivers/AnsiHandling/AnsiEscapeSequenceRequest.cs +++ b/Terminal.Gui/Drivers/AnsiHandling/AnsiEscapeSequenceRequest.cs @@ -1,5 +1,3 @@ -#nullable enable - namespace Terminal.Gui.Drivers; /// @@ -20,12 +18,12 @@ public class AnsiEscapeSequenceRequest : AnsiEscapeSequence /// public Action? Abandoned { get; init; } - /// /// Sends the to the raw output stream of the current . /// Only call this method from the main UI thread. You should use if /// sending many requests. /// - public void Send () { Application.Driver?.WriteRaw (Request); } + /// + public void Send (IDriver? driver) { driver?.WriteRaw (Request); } } diff --git a/Terminal.Gui/Drivers/AnsiHandling/AnsiMouseParser.cs b/Terminal.Gui/Drivers/AnsiHandling/AnsiMouseParser.cs index 7f3f82709..882863a6a 100644 --- a/Terminal.Gui/Drivers/AnsiHandling/AnsiMouseParser.cs +++ b/Terminal.Gui/Drivers/AnsiHandling/AnsiMouseParser.cs @@ -1,4 +1,3 @@ -#nullable enable using System.Text.RegularExpressions; namespace Terminal.Gui.Drivers; diff --git a/Terminal.Gui/Drivers/AnsiHandling/AnsiRequestScheduler.cs b/Terminal.Gui/Drivers/AnsiHandling/AnsiRequestScheduler.cs index 3f20af735..8fd50a4cc 100644 --- a/Terminal.Gui/Drivers/AnsiHandling/AnsiRequestScheduler.cs +++ b/Terminal.Gui/Drivers/AnsiHandling/AnsiRequestScheduler.cs @@ -1,4 +1,3 @@ -#nullable enable using System.Collections.Concurrent; namespace Terminal.Gui.Drivers; @@ -70,15 +69,16 @@ public class AnsiRequestScheduler /// Sends the immediately or queues it if there is already /// an outstanding request for the given . /// + /// /// /// if request was sent immediately. if it was queued. - public bool SendOrSchedule (AnsiEscapeSequenceRequest request) { return SendOrSchedule (request, true); } + public bool SendOrSchedule (IDriver? driver, AnsiEscapeSequenceRequest request) { return SendOrSchedule (driver, request, true); } - private bool SendOrSchedule (AnsiEscapeSequenceRequest request, bool addToQueue) + private bool SendOrSchedule (IDriver? driver, AnsiEscapeSequenceRequest request, bool addToQueue) { if (CanSend (request, out ReasonCannotSend reason)) { - Send (request); + Send (driver, request); return true; } @@ -91,7 +91,7 @@ public class AnsiRequestScheduler // Try again after evicting if (CanSend (request, out _)) { - Send (request); + Send (driver, request); return true; } @@ -142,6 +142,7 @@ public class AnsiRequestScheduler /// Identifies and runs any that can be sent based on the /// current outstanding requests of the parser. /// + /// /// /// Repeated requests to run the schedule over short period of time will be ignored. /// Pass to override this behaviour and force evaluation of outstanding requests. @@ -150,7 +151,7 @@ public class AnsiRequestScheduler /// if a request was found and run. /// if no outstanding requests or all have existing outstanding requests underway in parser. /// - public bool RunSchedule (bool force = false) + public bool RunSchedule (IDriver? driver, bool force = false) { if (!force && Now () - _lastRun < _runScheduleThrottle) { @@ -163,7 +164,7 @@ public class AnsiRequestScheduler if (opportunity != null) { // Give it another go - if (SendOrSchedule (opportunity.Item1, false)) + if (SendOrSchedule (driver, opportunity.Item1, false)) { _queuedRequests.Remove (opportunity); @@ -176,11 +177,11 @@ public class AnsiRequestScheduler return false; } - private void Send (AnsiEscapeSequenceRequest r) + private void Send (IDriver? driver, AnsiEscapeSequenceRequest r) { _lastSend.AddOrUpdate (r.Terminator!, _ => Now (), (_, _) => Now ()); _parser.ExpectResponse (r.Terminator, r.ResponseReceived, r.Abandoned, false); - r.Send (); + r.Send (driver); } private bool CanSend (AnsiEscapeSequenceRequest r, out ReasonCannotSend reason) diff --git a/Terminal.Gui/Drivers/AnsiHandling/AnsiResponseExpectation.cs b/Terminal.Gui/Drivers/AnsiHandling/AnsiResponseExpectation.cs index fd087de75..1c6539316 100644 --- a/Terminal.Gui/Drivers/AnsiHandling/AnsiResponseExpectation.cs +++ b/Terminal.Gui/Drivers/AnsiHandling/AnsiResponseExpectation.cs @@ -1,4 +1,3 @@ -#nullable enable namespace Terminal.Gui.Drivers; internal record AnsiResponseExpectation (string? Terminator, Action Response, Action? Abandoned) diff --git a/Terminal.Gui/Drivers/AnsiHandling/AnsiResponseParser.cs b/Terminal.Gui/Drivers/AnsiHandling/AnsiResponseParser.cs index 5bafbfe55..60f6d87f5 100644 --- a/Terminal.Gui/Drivers/AnsiHandling/AnsiResponseParser.cs +++ b/Terminal.Gui/Drivers/AnsiHandling/AnsiResponseParser.cs @@ -1,5 +1,3 @@ -#nullable enable - using Microsoft.Extensions.Logging; namespace Terminal.Gui.Drivers; diff --git a/Terminal.Gui/Drivers/AnsiHandling/EscSeqUtils/EscSeqReqStatus.cs b/Terminal.Gui/Drivers/AnsiHandling/EscSeqUtils/EscSeqReqStatus.cs index ab062e253..8943ab1bc 100644 --- a/Terminal.Gui/Drivers/AnsiHandling/EscSeqUtils/EscSeqReqStatus.cs +++ b/Terminal.Gui/Drivers/AnsiHandling/EscSeqUtils/EscSeqReqStatus.cs @@ -1,4 +1,3 @@ -#nullable enable namespace Terminal.Gui.Drivers; /// diff --git a/Terminal.Gui/Drivers/AnsiHandling/EscSeqUtils/EscSeqRequests.cs b/Terminal.Gui/Drivers/AnsiHandling/EscSeqUtils/EscSeqRequests.cs index 6e69f6a12..7732bd319 100644 --- a/Terminal.Gui/Drivers/AnsiHandling/EscSeqUtils/EscSeqRequests.cs +++ b/Terminal.Gui/Drivers/AnsiHandling/EscSeqUtils/EscSeqRequests.cs @@ -1,5 +1,3 @@ -#nullable enable - namespace Terminal.Gui.Drivers; /// diff --git a/Terminal.Gui/Drivers/AnsiHandling/EscSeqUtils/EscSeqUtils.cs b/Terminal.Gui/Drivers/AnsiHandling/EscSeqUtils/EscSeqUtils.cs index 6257d1d66..3f9d27d0f 100644 --- a/Terminal.Gui/Drivers/AnsiHandling/EscSeqUtils/EscSeqUtils.cs +++ b/Terminal.Gui/Drivers/AnsiHandling/EscSeqUtils/EscSeqUtils.cs @@ -1,4 +1,3 @@ -#nullable enable using System.Diagnostics; using System.Globalization; diff --git a/Terminal.Gui/Drivers/AnsiHandling/GenericHeld.cs b/Terminal.Gui/Drivers/AnsiHandling/GenericHeld.cs index 6373003fa..6339b4ee2 100644 --- a/Terminal.Gui/Drivers/AnsiHandling/GenericHeld.cs +++ b/Terminal.Gui/Drivers/AnsiHandling/GenericHeld.cs @@ -1,4 +1,3 @@ -#nullable enable namespace Terminal.Gui.Drivers; /// diff --git a/Terminal.Gui/Drivers/AnsiHandling/IAnsiResponseParser.cs b/Terminal.Gui/Drivers/AnsiHandling/IAnsiResponseParser.cs index cfdb775f0..29892a2da 100644 --- a/Terminal.Gui/Drivers/AnsiHandling/IAnsiResponseParser.cs +++ b/Terminal.Gui/Drivers/AnsiHandling/IAnsiResponseParser.cs @@ -1,4 +1,3 @@ -#nullable enable namespace Terminal.Gui.Drivers; /// diff --git a/Terminal.Gui/Drivers/AnsiHandling/IHeld.cs b/Terminal.Gui/Drivers/AnsiHandling/IHeld.cs index 369ef4732..e6d5a3a06 100644 --- a/Terminal.Gui/Drivers/AnsiHandling/IHeld.cs +++ b/Terminal.Gui/Drivers/AnsiHandling/IHeld.cs @@ -1,4 +1,3 @@ -#nullable enable namespace Terminal.Gui.Drivers; /// diff --git a/Terminal.Gui/Drivers/AnsiHandling/Keyboard/AnsiKeyboardParser.cs b/Terminal.Gui/Drivers/AnsiHandling/Keyboard/AnsiKeyboardParser.cs index ee41d6cca..89ef61a58 100644 --- a/Terminal.Gui/Drivers/AnsiHandling/Keyboard/AnsiKeyboardParser.cs +++ b/Terminal.Gui/Drivers/AnsiHandling/Keyboard/AnsiKeyboardParser.cs @@ -1,4 +1,3 @@ -#nullable enable namespace Terminal.Gui.Drivers; /// diff --git a/Terminal.Gui/Drivers/AnsiHandling/Keyboard/AnsiKeyboardParserPattern.cs b/Terminal.Gui/Drivers/AnsiHandling/Keyboard/AnsiKeyboardParserPattern.cs index 62b0acb64..51011124a 100644 --- a/Terminal.Gui/Drivers/AnsiHandling/Keyboard/AnsiKeyboardParserPattern.cs +++ b/Terminal.Gui/Drivers/AnsiHandling/Keyboard/AnsiKeyboardParserPattern.cs @@ -1,5 +1,3 @@ -#nullable enable - namespace Terminal.Gui.Drivers; /// diff --git a/Terminal.Gui/Drivers/AnsiHandling/Keyboard/CsiCursorPattern.cs b/Terminal.Gui/Drivers/AnsiHandling/Keyboard/CsiCursorPattern.cs index 744658a76..61adedd62 100644 --- a/Terminal.Gui/Drivers/AnsiHandling/Keyboard/CsiCursorPattern.cs +++ b/Terminal.Gui/Drivers/AnsiHandling/Keyboard/CsiCursorPattern.cs @@ -1,4 +1,3 @@ -#nullable enable using System.Text.RegularExpressions; namespace Terminal.Gui.Drivers; diff --git a/Terminal.Gui/Drivers/AnsiHandling/Keyboard/CsiKeyPattern.cs b/Terminal.Gui/Drivers/AnsiHandling/Keyboard/CsiKeyPattern.cs index f0fb3b20b..ed2bcecbe 100644 --- a/Terminal.Gui/Drivers/AnsiHandling/Keyboard/CsiKeyPattern.cs +++ b/Terminal.Gui/Drivers/AnsiHandling/Keyboard/CsiKeyPattern.cs @@ -1,4 +1,3 @@ -#nullable enable using System.Text.RegularExpressions; namespace Terminal.Gui.Drivers; diff --git a/Terminal.Gui/Drivers/AnsiHandling/Keyboard/EscAsAltPattern.cs b/Terminal.Gui/Drivers/AnsiHandling/Keyboard/EscAsAltPattern.cs index a9b16e90a..fd6b54321 100644 --- a/Terminal.Gui/Drivers/AnsiHandling/Keyboard/EscAsAltPattern.cs +++ b/Terminal.Gui/Drivers/AnsiHandling/Keyboard/EscAsAltPattern.cs @@ -1,4 +1,3 @@ -#nullable enable using System.Text.RegularExpressions; namespace Terminal.Gui.Drivers; diff --git a/Terminal.Gui/Drivers/AnsiHandling/Keyboard/Ss3Pattern.cs b/Terminal.Gui/Drivers/AnsiHandling/Keyboard/Ss3Pattern.cs index 988b584f1..cf2804072 100644 --- a/Terminal.Gui/Drivers/AnsiHandling/Keyboard/Ss3Pattern.cs +++ b/Terminal.Gui/Drivers/AnsiHandling/Keyboard/Ss3Pattern.cs @@ -1,4 +1,3 @@ -#nullable enable using System.Text.RegularExpressions; namespace Terminal.Gui.Drivers; diff --git a/Terminal.Gui/Drivers/AnsiHandling/Osc8UrlLinker.cs b/Terminal.Gui/Drivers/AnsiHandling/Osc8UrlLinker.cs index 7a3e41a6d..d4cd43065 100644 --- a/Terminal.Gui/Drivers/AnsiHandling/Osc8UrlLinker.cs +++ b/Terminal.Gui/Drivers/AnsiHandling/Osc8UrlLinker.cs @@ -1,4 +1,3 @@ -#nullable enable namespace Terminal.Gui.Drivers; diff --git a/Terminal.Gui/Drivers/AnsiHandling/ReasonCannotSend.cs b/Terminal.Gui/Drivers/AnsiHandling/ReasonCannotSend.cs index 675c9ff64..ba0aa399c 100644 --- a/Terminal.Gui/Drivers/AnsiHandling/ReasonCannotSend.cs +++ b/Terminal.Gui/Drivers/AnsiHandling/ReasonCannotSend.cs @@ -1,4 +1,3 @@ -#nullable enable namespace Terminal.Gui.Drivers; internal enum ReasonCannotSend diff --git a/Terminal.Gui/Drivers/AnsiHandling/StringHeld.cs b/Terminal.Gui/Drivers/AnsiHandling/StringHeld.cs index 3202410ab..79e922098 100644 --- a/Terminal.Gui/Drivers/AnsiHandling/StringHeld.cs +++ b/Terminal.Gui/Drivers/AnsiHandling/StringHeld.cs @@ -1,4 +1,3 @@ -#nullable enable namespace Terminal.Gui.Drivers; /// diff --git a/Terminal.Gui/Drivers/ComponentFactoryImpl.cs b/Terminal.Gui/Drivers/ComponentFactoryImpl.cs index d09099954..04f79a88e 100644 --- a/Terminal.Gui/Drivers/ComponentFactoryImpl.cs +++ b/Terminal.Gui/Drivers/ComponentFactoryImpl.cs @@ -1,5 +1,4 @@ -#nullable enable -using System.Collections.Concurrent; +using System.Collections.Concurrent; namespace Terminal.Gui.Drivers; diff --git a/Terminal.Gui/Drivers/CursorVisibility.cs b/Terminal.Gui/Drivers/CursorVisibility.cs index ca86e9a0a..7d0c5d9f4 100644 --- a/Terminal.Gui/Drivers/CursorVisibility.cs +++ b/Terminal.Gui/Drivers/CursorVisibility.cs @@ -1,4 +1,3 @@ -#nullable enable namespace Terminal.Gui.Drivers; /// Terminal Cursor Visibility settings. diff --git a/Terminal.Gui/Drivers/DotNetDriver/NetComponentFactory.cs b/Terminal.Gui/Drivers/DotNetDriver/NetComponentFactory.cs index 669c6efce..46b8b9efb 100644 --- a/Terminal.Gui/Drivers/DotNetDriver/NetComponentFactory.cs +++ b/Terminal.Gui/Drivers/DotNetDriver/NetComponentFactory.cs @@ -1,4 +1,3 @@ -#nullable enable using System.Collections.Concurrent; namespace Terminal.Gui.Drivers; diff --git a/Terminal.Gui/Drivers/DotNetDriver/NetInput.cs b/Terminal.Gui/Drivers/DotNetDriver/NetInput.cs index b44c59522..026689a45 100644 --- a/Terminal.Gui/Drivers/DotNetDriver/NetInput.cs +++ b/Terminal.Gui/Drivers/DotNetDriver/NetInput.cs @@ -1,5 +1,4 @@ -using Microsoft.Extensions.Logging; - +#nullable disable namespace Terminal.Gui.Drivers; /// diff --git a/Terminal.Gui/Drivers/DotNetDriver/NetOutput.cs b/Terminal.Gui/Drivers/DotNetDriver/NetOutput.cs index 8fbd11ba1..4f8ab1fc0 100644 --- a/Terminal.Gui/Drivers/DotNetDriver/NetOutput.cs +++ b/Terminal.Gui/Drivers/DotNetDriver/NetOutput.cs @@ -1,5 +1,3 @@ -using Microsoft.Extensions.Logging; - namespace Terminal.Gui.Drivers; /// diff --git a/Terminal.Gui/Drivers/DotNetDriver/NetWinVTConsole.cs b/Terminal.Gui/Drivers/DotNetDriver/NetWinVTConsole.cs index d6730e044..36cbf0e2a 100644 --- a/Terminal.Gui/Drivers/DotNetDriver/NetWinVTConsole.cs +++ b/Terminal.Gui/Drivers/DotNetDriver/NetWinVTConsole.cs @@ -1,4 +1,3 @@ -#nullable enable using System.Runtime.InteropServices; namespace Terminal.Gui.Drivers; diff --git a/Terminal.Gui/Drivers/DriverImpl.cs b/Terminal.Gui/Drivers/DriverImpl.cs index db1918459..ee125c143 100644 --- a/Terminal.Gui/Drivers/DriverImpl.cs +++ b/Terminal.Gui/Drivers/DriverImpl.cs @@ -1,5 +1,4 @@ -#nullable enable -using System.Runtime.InteropServices; +using System.Runtime.InteropServices; namespace Terminal.Gui.Drivers; @@ -75,9 +74,7 @@ internal class DriverImpl : IDriver CreateClipboard (); } - /// - /// The event fired when the screen changes (size, position, etc.). - /// + /// public event EventHandler? SizeChanged; /// @@ -89,7 +86,6 @@ internal class DriverImpl : IDriver /// public ISizeMonitor SizeMonitor { get; } - private void CreateClipboard () { if (InputProcessor.DriverName is { } && InputProcessor.DriverName.Contains ("fake")) @@ -120,27 +116,18 @@ internal class DriverImpl : IDriver // Clipboard is set to FakeClipboard at initialization } - /// Gets the location and size of the terminal screen. - public Rectangle Screen - { - get - { - //if (Application.RunningUnitTests && _output is WindowsConsoleOutput or NetOutput) - //{ - // // In unit tests, we don't have a real output, so we return an empty rectangle. - // return Rectangle.Empty; - //} + /// - return new (0, 0, OutputBuffer.Cols, OutputBuffer.Rows); - } - } + public Rectangle Screen => - /// - /// Sets the screen size for testing purposes. Only supported by FakeDriver. - /// - /// The new width in columns. - /// The new height in rows. - /// Thrown when called on non-FakeDriver instances. + //if (Application.RunningUnitTests && _output is WindowsConsoleOutput or NetOutput) + //{ + // // In unit tests, we don't have a real output, so we return an empty rectangle. + // return Rectangle.Empty; + //} + new (0, 0, OutputBuffer.Cols, OutputBuffer.Rows); + + /// public virtual void SetScreenSize (int width, int height) { OutputBuffer.SetSize (width, height); @@ -148,64 +135,60 @@ internal class DriverImpl : IDriver SizeChanged?.Invoke (this, new (new (width, height))); } - /// - /// Gets or sets the clip rectangle that and are subject - /// to. - /// - /// The rectangle describing the of region. + /// + public Region? Clip { get => OutputBuffer.Clip; set => OutputBuffer.Clip = value; } - /// Get the operating system clipboard. + /// + public IClipboard? Clipboard { get; private set; } = new FakeClipboard (); - /// - /// Gets the column last set by . and are used by - /// and to determine where to add content. - /// + /// + public int Col => OutputBuffer.Col; - /// The number of columns visible in the terminal. + /// + public int Cols { get => OutputBuffer.Cols; set => OutputBuffer.Cols = value; } - /// - /// The contents of the application output. The driver outputs this buffer to the terminal. - /// The format of the array is rows, columns. The first index is the row, the second index is the column. - /// + /// + public Cell [,]? Contents { get => OutputBuffer.Contents; set => OutputBuffer.Contents = value; } - /// The leftmost column in the terminal. + /// + public int Left { get => OutputBuffer.Left; set => OutputBuffer.Left = value; } - /// - /// Gets the row last set by . and are used by - /// and to determine where to add content. - /// + /// + public int Row => OutputBuffer.Row; - /// The number of rows visible in the terminal. + /// + public int Rows { get => OutputBuffer.Rows; set => OutputBuffer.Rows = value; } - /// The topmost row in the terminal. + /// + public int Top { get => OutputBuffer.Top; @@ -214,75 +197,33 @@ internal class DriverImpl : IDriver // TODO: Probably not everyone right? - /// Gets whether the supports TrueColor output. + /// + public bool SupportsTrueColor => true; - // TODO: Currently ignored - /// - /// Gets or sets whether the should use 16 colors instead of the default TrueColors. - /// See to change this setting via . - /// - /// - /// - /// Will be forced to if is - /// , indicating that the cannot support TrueColor. - /// - /// + /// + public bool Force16Colors { get => Application.Force16Colors || !SupportsTrueColor; set => Application.Force16Colors = value || !SupportsTrueColor; } - /// - /// The that will be used for the next or - /// - /// call. - /// + /// + public Attribute CurrentAttribute { get => OutputBuffer.CurrentAttribute; set => OutputBuffer.CurrentAttribute = value; } - /// Adds the specified rune to the display at the current cursor position. - /// - /// - /// When the method returns, will be incremented by the number of columns - /// required, even if the new column value is outside of the - /// or screen - /// dimensions defined by . - /// - /// - /// If requires more than one column, and plus the number - /// of columns - /// needed exceeds the or screen dimensions, the default Unicode replacement - /// character (U+FFFD) - /// will be added instead. - /// - /// - /// Rune to add. + /// public void AddRune (Rune rune) { OutputBuffer.AddRune (rune); } - /// - /// Adds the specified to the display at the current cursor position. This method is a - /// convenience method that calls with the - /// constructor. - /// - /// Character to add. + /// public void AddRune (char c) { OutputBuffer.AddRune (c); } - /// Adds the to the display at the cursor position. - /// - /// - /// When the method returns, will be incremented by the number of columns - /// required, unless the new column value is outside of the - /// or screen - /// dimensions defined by . - /// - /// If requires more columns than are available, the output will be clipped. - /// - /// String. + /// public void AddStr (string str) { OutputBuffer.AddStr (str); } /// Clears the of the driver. @@ -292,28 +233,13 @@ internal class DriverImpl : IDriver ClearedContents?.Invoke (this, new MouseEventArgs ()); } - /// - /// Raised each time is called. For benchmarking. - /// + /// public event EventHandler? ClearedContents; - /// - /// Fills the specified rectangle with the specified rune, using - /// - /// - /// The value of is honored. Any parts of the rectangle not in the clip will not be - /// drawn. - /// - /// The Screen-relative rectangle. - /// The Rune used to fill the rectangle + /// public void FillRect (Rectangle rect, Rune rune = default) { OutputBuffer.FillRect (rect, rune); } - /// - /// Fills the specified rectangle with the specified . This method is a convenience method - /// that calls . - /// - /// - /// + /// public void FillRect (Rectangle rect, char c) { OutputBuffer.FillRect (rect, c); } /// @@ -324,48 +250,18 @@ internal class DriverImpl : IDriver return type; } - /// Tests if the specified rune is supported by the driver. - /// - /// - /// if the rune can be properly presented; if the driver does not - /// support displaying this rune. - /// - public bool IsRuneSupported (Rune rune) { return Rune.IsValid (rune.Value); } + /// + public bool IsRuneSupported (Rune rune) => Rune.IsValid (rune.Value); - /// Tests whether the specified coordinate are valid for drawing the specified Rune. - /// Used to determine if one or two columns are required. - /// The column. - /// The row. - /// - /// if the coordinate is outside the screen bounds or outside of - /// . - /// otherwise. - /// - public bool IsValidLocation (Rune rune, int col, int row) { return OutputBuffer.IsValidLocation (rune, col, row); } + /// + public bool IsValidLocation (Rune rune, int col, int row) => OutputBuffer.IsValidLocation (rune, col, row); - /// - /// Updates and to the specified column and row in - /// . - /// Used by and to determine - /// where to add content. - /// - /// - /// This does not move the cursor on the screen, it only updates the internal state of the driver. - /// - /// If or are negative or beyond - /// and - /// , the method still sets those properties. - /// - /// - /// Column to move to. - /// Row to move to. + /// public void Move (int col, int row) { OutputBuffer.Move (col, row); } // TODO: Probably part of output - /// Sets the terminal cursor visibility. - /// The wished - /// upon success + /// public bool SetCursorVisibility (CursorVisibility visibility) { _lastCursor = visibility; @@ -415,31 +311,22 @@ internal class DriverImpl : IDriver Logging.Error ($"Error suspending terminal: {ex.Message}"); } - Application.LayoutAndDraw (); - - Console.Out.Write (EscSeqUtils.CSI_EnableMouseEvents); } - /// - /// Sets the position of the terminal cursor to and - /// . - /// + /// public void UpdateCursor () { _output.SetCursorPosition (Col, Row); } - /// Initializes the driver + /// public void Init () { throw new NotSupportedException (); } - /// Ends the execution of the console driver. + /// public void End () { // TODO: Nope } - /// Selects the specified attribute as the attribute to use for future calls to AddRune and AddString. - /// Implementations should call base.SetAttribute(c). - /// C. - /// The previously set Attribute. + /// public Attribute SetAttribute (Attribute newAttribute) { Attribute currentAttribute = OutputBuffer.CurrentAttribute; @@ -448,51 +335,29 @@ internal class DriverImpl : IDriver return currentAttribute; } - /// Gets the current . - /// The current attribute. - public Attribute GetAttribute () { return OutputBuffer.CurrentAttribute; } + /// + public Attribute GetAttribute () => OutputBuffer.CurrentAttribute; /// Event fired when a key is pressed down. This is a precursor to . public event EventHandler? KeyDown; - /// Event fired when a key is released. - /// - /// Drivers that do not support key release events will fire this event after - /// processing is - /// complete. - /// + /// public event EventHandler? KeyUp; /// Event fired when a mouse event occurs. public event EventHandler? MouseEvent; - /// - /// Provide proper writing to send escape sequence recognized by the . - /// - /// + /// public void WriteRaw (string ansi) { _output.Write (ansi); } /// - public void EnqueueKeyEvent (Key key) - { - InputProcessor.EnqueueKeyDownEvent (key); - } + public void EnqueueKeyEvent (Key key) { InputProcessor.EnqueueKeyDownEvent (key); } - /// - /// Queues the specified ANSI escape sequence request for execution. - /// - /// The ANSI request to queue. - /// - /// The request is sent immediately if possible, or queued for later execution - /// by the to prevent overwhelming the console. - /// - public void QueueAnsiRequest (AnsiEscapeSequenceRequest request) { _ansiRequestScheduler.SendOrSchedule (request); } + /// + public void QueueAnsiRequest (AnsiEscapeSequenceRequest request) { _ansiRequestScheduler.SendOrSchedule (this, request); } - /// - /// Gets the instance used by this driver. - /// - /// The ANSI request scheduler. - public AnsiRequestScheduler GetRequestScheduler () { return _ansiRequestScheduler; } + /// + public AnsiRequestScheduler GetRequestScheduler () => _ansiRequestScheduler; /// public void Refresh () @@ -500,8 +365,51 @@ internal class DriverImpl : IDriver // No need we will always draw when dirty } - public string? GetName () + /// + public string? GetName () => InputProcessor.DriverName?.ToLowerInvariant (); + + /// + public new string ToString () { - return InputProcessor.DriverName?.ToLowerInvariant (); + StringBuilder sb = new (); + + Cell [,] contents = Contents!; + + for (var r = 0; r < Rows; r++) + { + for (var c = 0; c < Cols; c++) + { + Rune rune = contents [r, c].Rune; + + if (rune.DecodeSurrogatePair (out char []? sp)) + { + sb.Append (sp); + } + else + { + sb.Append ((char)rune.Value); + } + + if (rune.GetColumns () > 1) + { + c++; + } + + // See Issue #2616 + //foreach (var combMark in contents [r, c].CombiningMarks) { + // sb.Append ((char)combMark.Value); + //} + } + + sb.AppendLine (); + } + + return sb.ToString (); + } + + /// + public string ToAnsi () + { + return _output.ToAnsi (OutputBuffer); } } diff --git a/Terminal.Gui/Drivers/FakeDriver/FakeClipboard.cs b/Terminal.Gui/Drivers/FakeDriver/FakeClipboard.cs index 6951b7923..9a7bc4d57 100644 --- a/Terminal.Gui/Drivers/FakeDriver/FakeClipboard.cs +++ b/Terminal.Gui/Drivers/FakeDriver/FakeClipboard.cs @@ -1,4 +1,3 @@ -#nullable enable namespace Terminal.Gui.Drivers; /// diff --git a/Terminal.Gui/Drivers/FakeDriver/FakeComponentFactory.cs b/Terminal.Gui/Drivers/FakeDriver/FakeComponentFactory.cs index e7ce72f3d..5f4284bdc 100644 --- a/Terminal.Gui/Drivers/FakeDriver/FakeComponentFactory.cs +++ b/Terminal.Gui/Drivers/FakeDriver/FakeComponentFactory.cs @@ -1,4 +1,3 @@ -#nullable enable using System.Collections.Concurrent; namespace Terminal.Gui.Drivers; diff --git a/Terminal.Gui/Drivers/FakeDriver/FakeInput.cs b/Terminal.Gui/Drivers/FakeDriver/FakeInput.cs index 76093e785..b8a0a5240 100644 --- a/Terminal.Gui/Drivers/FakeDriver/FakeInput.cs +++ b/Terminal.Gui/Drivers/FakeDriver/FakeInput.cs @@ -1,4 +1,3 @@ -#nullable enable using System.Collections.Concurrent; namespace Terminal.Gui.Drivers; @@ -44,4 +43,4 @@ public class FakeInput : InputImpl, ITestableInput if (Application.MainThreadId is { }) { // Application is running - use Invoke to defer to next iteration - Application.Invoke (() => RaiseMouseEvent (mouseEvent)); + ApplicationImpl.Instance.Invoke ((_) => RaiseMouseEvent (mouseEvent)); } else { diff --git a/Terminal.Gui/Drivers/FakeDriver/FakeOutput.cs b/Terminal.Gui/Drivers/FakeDriver/FakeOutput.cs index 0722079fe..8fd790f19 100644 --- a/Terminal.Gui/Drivers/FakeDriver/FakeOutput.cs +++ b/Terminal.Gui/Drivers/FakeDriver/FakeOutput.cs @@ -1,4 +1,3 @@ -#nullable enable using System; namespace Terminal.Gui.Drivers; @@ -87,8 +86,29 @@ public class FakeOutput : OutputBase, IOutput /// protected override void AppendOrWriteAttribute (StringBuilder output, Attribute attr, TextStyle redrawTextStyle) { - // For testing, we can skip the actual color/style output - // or capture it if needed for verification + if (Application.Force16Colors) + { + output.Append (EscSeqUtils.CSI_SetForegroundColor (attr.Foreground.GetAnsiColorCode ())); + output.Append (EscSeqUtils.CSI_SetBackgroundColor (attr.Background.GetAnsiColorCode ())); + } + else + { + EscSeqUtils.CSI_AppendForegroundColorRGB ( + output, + attr.Foreground.R, + attr.Foreground.G, + attr.Foreground.B + ); + + EscSeqUtils.CSI_AppendBackgroundColorRGB ( + output, + attr.Background.R, + attr.Background.G, + attr.Background.B + ); + } + + EscSeqUtils.CSI_AppendTextStyleChange (output, redrawTextStyle, attr.Style); } /// diff --git a/Terminal.Gui/Drivers/IComponentFactory.cs b/Terminal.Gui/Drivers/IComponentFactory.cs index 7122c4af3..d58a95f68 100644 --- a/Terminal.Gui/Drivers/IComponentFactory.cs +++ b/Terminal.Gui/Drivers/IComponentFactory.cs @@ -1,5 +1,4 @@ -#nullable enable -using System.Collections.Concurrent; +using System.Collections.Concurrent; namespace Terminal.Gui.Drivers; diff --git a/Terminal.Gui/Drivers/IDriver.cs b/Terminal.Gui/Drivers/IDriver.cs index 32af99b05..090c192a4 100644 --- a/Terminal.Gui/Drivers/IDriver.cs +++ b/Terminal.Gui/Drivers/IDriver.cs @@ -1,4 +1,3 @@ -#nullable enable namespace Terminal.Gui.Drivers; @@ -295,4 +294,18 @@ public interface IDriver /// /// public AnsiRequestScheduler GetRequestScheduler (); + + + /// + /// Gets a string representation of . + /// + /// + public string ToString (); + + /// + /// Gets an ANSI escape sequence representation of . This is the + /// same output as would be written to the terminal to recreate the current screen contents. + /// + /// + public string ToAnsi (); } diff --git a/Terminal.Gui/Drivers/IInput.cs b/Terminal.Gui/Drivers/IInput.cs index 0b2ec7d41..c4a0af159 100644 --- a/Terminal.Gui/Drivers/IInput.cs +++ b/Terminal.Gui/Drivers/IInput.cs @@ -1,5 +1,4 @@ -#nullable enable -using System.Collections.Concurrent; +using System.Collections.Concurrent; namespace Terminal.Gui.Drivers; diff --git a/Terminal.Gui/Drivers/IInputProcessor.cs b/Terminal.Gui/Drivers/IInputProcessor.cs index d0c0284b8..9c800946c 100644 --- a/Terminal.Gui/Drivers/IInputProcessor.cs +++ b/Terminal.Gui/Drivers/IInputProcessor.cs @@ -1,5 +1,4 @@ -#nullable enable - + namespace Terminal.Gui.Drivers; /// diff --git a/Terminal.Gui/Drivers/IOutput.cs b/Terminal.Gui/Drivers/IOutput.cs index bc04dfaa6..0eb647dec 100644 --- a/Terminal.Gui/Drivers/IOutput.cs +++ b/Terminal.Gui/Drivers/IOutput.cs @@ -53,4 +53,12 @@ public interface IOutput : IDisposable /// /// void Write (IOutputBuffer buffer); + + /// + /// Generates an ANSI escape sequence string representation of the given contents. + /// This is the same output that would be written to the terminal to recreate the current screen contents. + /// + /// The output buffer to convert to ANSI. + /// A string containing ANSI escape sequences representing the buffer contents. + string ToAnsi (IOutputBuffer buffer); } diff --git a/Terminal.Gui/Drivers/IOutputBuffer.cs b/Terminal.Gui/Drivers/IOutputBuffer.cs index 2b8991593..387fde46c 100644 --- a/Terminal.Gui/Drivers/IOutputBuffer.cs +++ b/Terminal.Gui/Drivers/IOutputBuffer.cs @@ -1,5 +1,4 @@ -#nullable enable - + namespace Terminal.Gui.Drivers; /// diff --git a/Terminal.Gui/Drivers/ISizeMonitor.cs b/Terminal.Gui/Drivers/ISizeMonitor.cs index 602d5e4b8..df8273bfd 100644 --- a/Terminal.Gui/Drivers/ISizeMonitor.cs +++ b/Terminal.Gui/Drivers/ISizeMonitor.cs @@ -1,5 +1,4 @@ -#nullable enable - + namespace Terminal.Gui.Drivers; /// diff --git a/Terminal.Gui/Drivers/InputImpl.cs b/Terminal.Gui/Drivers/InputImpl.cs index d340b3d85..b70df2b44 100644 --- a/Terminal.Gui/Drivers/InputImpl.cs +++ b/Terminal.Gui/Drivers/InputImpl.cs @@ -1,5 +1,4 @@ -#nullable enable -using System.Collections.Concurrent; +using System.Collections.Concurrent; namespace Terminal.Gui.Drivers; @@ -86,4 +85,4 @@ public abstract class InputImpl : IInput /// public virtual void Dispose () { } -} \ No newline at end of file +} diff --git a/Terminal.Gui/Drivers/InputProcessorImpl.cs b/Terminal.Gui/Drivers/InputProcessorImpl.cs index f79a96364..24c249a84 100644 --- a/Terminal.Gui/Drivers/InputProcessorImpl.cs +++ b/Terminal.Gui/Drivers/InputProcessorImpl.cs @@ -1,5 +1,4 @@ -#nullable enable -using System.Collections.Concurrent; +using System.Collections.Concurrent; using Microsoft.Extensions.Logging; namespace Terminal.Gui.Drivers; diff --git a/Terminal.Gui/Drivers/KeyCode.cs b/Terminal.Gui/Drivers/KeyCode.cs index eb87a7e92..431508e5f 100644 --- a/Terminal.Gui/Drivers/KeyCode.cs +++ b/Terminal.Gui/Drivers/KeyCode.cs @@ -1,4 +1,3 @@ -#nullable enable namespace Terminal.Gui.Drivers; /// diff --git a/Terminal.Gui/Drivers/MouseButtonStateEx.cs b/Terminal.Gui/Drivers/MouseButtonStateEx.cs index e6e2a1e96..1aba69677 100644 --- a/Terminal.Gui/Drivers/MouseButtonStateEx.cs +++ b/Terminal.Gui/Drivers/MouseButtonStateEx.cs @@ -1,5 +1,4 @@ -#nullable enable - + namespace Terminal.Gui.Drivers; /// diff --git a/Terminal.Gui/Drivers/MouseInterpreter.cs b/Terminal.Gui/Drivers/MouseInterpreter.cs index cb585ed9f..f5848ab65 100644 --- a/Terminal.Gui/Drivers/MouseInterpreter.cs +++ b/Terminal.Gui/Drivers/MouseInterpreter.cs @@ -1,5 +1,4 @@ -#nullable enable - + namespace Terminal.Gui.Drivers; diff --git a/Terminal.Gui/Drivers/OutputBase.cs b/Terminal.Gui/Drivers/OutputBase.cs index ac43dc93d..ca8b92c01 100644 --- a/Terminal.Gui/Drivers/OutputBase.cs +++ b/Terminal.Gui/Drivers/OutputBase.cs @@ -1,4 +1,3 @@ -#nullable enable namespace Terminal.Gui.Drivers; /// @@ -32,9 +31,6 @@ public abstract class OutputBase CursorVisibility? savedVisibility = _cachedCursorVisibility; SetCursorVisibility (CursorVisibility.Invisible); - const int MAX_CHARS_PER_RUNE = 2; - Span runeBuffer = stackalloc char [MAX_CHARS_PER_RUNE]; - for (int row = top; row < rows; row++) { if (!SetCursorPositionImpl (0, row)) @@ -75,32 +71,14 @@ public abstract class OutputBase lastCol = col; } - Attribute? attribute = buffer.Contents [row, col].Attribute; - - if (attribute is { }) - { - Attribute attr = attribute.Value; - - // Performance: Only send the escape sequence if the attribute has changed. - if (attr != redrawAttr) - { - redrawAttr = attr; - - AppendOrWriteAttribute (output, attr, _redrawTextStyle); - - _redrawTextStyle = attr.Style; - } - } + Cell cell = buffer.Contents [row, col]; + AppendCellAnsi (cell, output, ref redrawAttr, ref _redrawTextStyle, cols, ref col); outputWidth++; - // Avoid Rune.ToString() by appending the rune chars. - Rune rune = buffer.Contents [row, col].Rune; - int runeCharsWritten = rune.EncodeToUtf16 (runeBuffer); - ReadOnlySpan runeChars = runeBuffer [..runeCharsWritten]; - output.Append (runeChars); - - if (buffer.Contents [row, col].CombiningMarks.Count > 0) + // Handle special cases that AppendCellAnsi doesn't cover + Rune rune = cell.Rune; + if (cell.CombiningMarks.Count > 0) { // AtlasEngine does not support NON-NORMALIZED combining marks in a way // compatible with the driver architecture. Any CMs (except in the first col) @@ -170,6 +148,105 @@ public abstract class OutputBase /// protected abstract void Write (StringBuilder output); + /// + /// Builds ANSI escape sequences for the specified rectangular region of the buffer. + /// + /// The output buffer to build ANSI for. + /// The starting row (inclusive). + /// The ending row (exclusive). + /// The starting column (inclusive). + /// The ending column (exclusive). + /// The StringBuilder to append ANSI sequences to. + /// The last attribute used, for optimization. + /// Predicate to determine which cells to include. If null, includes all cells. + /// Whether to add newlines between rows. + protected void BuildAnsiForRegion ( + IOutputBuffer buffer, + int startRow, + int endRow, + int startCol, + int endCol, + StringBuilder output, + ref Attribute? lastAttr, + Func? includeCellPredicate = null, + bool addNewlines = true + ) + { + TextStyle redrawTextStyle = TextStyle.None; + + for (int row = startRow; row < endRow; row++) + { + for (int col = startCol; col < endCol; col++) + { + if (includeCellPredicate != null && !includeCellPredicate (row, col)) + { + continue; + } + + Cell cell = buffer.Contents![row, col]; + AppendCellAnsi (cell, output, ref lastAttr, ref redrawTextStyle, endCol, ref col); + } + + // Add newline at end of row if requested + if (addNewlines) + { + output.AppendLine (); + } + } + } + + /// + /// Appends ANSI sequences for a single cell to the output. + /// + /// The cell to append ANSI for. + /// The StringBuilder to append to. + /// The last attribute used, updated if the cell's attribute is different. + /// The current text style for optimization. + /// The maximum column, used for wide character handling. + /// The current column, updated for wide characters. + protected void AppendCellAnsi (Cell cell, StringBuilder output, ref Attribute? lastAttr, ref TextStyle redrawTextStyle, int maxCol, ref int currentCol) + { + Attribute? attribute = cell.Attribute; + + // Add ANSI escape sequence for attribute change + if (attribute.HasValue && attribute.Value != lastAttr) + { + lastAttr = attribute.Value; + AppendOrWriteAttribute (output, attribute.Value, redrawTextStyle); + redrawTextStyle = attribute.Value.Style; + } + + // Add the character + const int MAX_CHARS_PER_RUNE = 2; + Span runeBuffer = stackalloc char [MAX_CHARS_PER_RUNE]; + Rune rune = cell.Rune; + int runeCharsWritten = rune.EncodeToUtf16 (runeBuffer); + ReadOnlySpan runeChars = runeBuffer [..runeCharsWritten]; + output.Append (runeChars); + + // Handle wide characters + if (rune.GetColumns () > 1 && currentCol + 1 < maxCol) + { + currentCol++; // Skip next cell for wide character + } + } + + /// + /// Generates an ANSI escape sequence string representation of the given contents. + /// This is the same output that would be written to the terminal to recreate the current screen contents. + /// + /// The output buffer to convert to ANSI. + /// A string containing ANSI escape sequences representing the buffer contents. + public string ToAnsi (IOutputBuffer buffer) + { + var output = new StringBuilder (); + Attribute? lastAttr = null; + + BuildAnsiForRegion (buffer, 0, buffer.Rows, 0, buffer.Cols, output, ref lastAttr); + + return output.ToString (); + } + private void WriteToConsole (StringBuilder output, ref int lastCol, int row, ref int outputWidth) { SetCursorPositionImpl (lastCol, row); diff --git a/Terminal.Gui/Drivers/OutputBufferImpl.cs b/Terminal.Gui/Drivers/OutputBufferImpl.cs index ee2493d60..d12eb7991 100644 --- a/Terminal.Gui/Drivers/OutputBufferImpl.cs +++ b/Terminal.Gui/Drivers/OutputBufferImpl.cs @@ -1,5 +1,4 @@ -#nullable enable -using System.Diagnostics; +using System.Diagnostics; namespace Terminal.Gui.Drivers; diff --git a/Terminal.Gui/Drivers/Platform.cs b/Terminal.Gui/Drivers/Platform.cs index 4b14ba053..4b7b20a18 100644 --- a/Terminal.Gui/Drivers/Platform.cs +++ b/Terminal.Gui/Drivers/Platform.cs @@ -80,4 +80,4 @@ internal static class Platform [DllImport ("libc")] private static extern int uname (nint buf); -} \ No newline at end of file +} diff --git a/Terminal.Gui/Drivers/SizeMonitorImpl.cs b/Terminal.Gui/Drivers/SizeMonitorImpl.cs index 409f27617..f41b50d7e 100644 --- a/Terminal.Gui/Drivers/SizeMonitorImpl.cs +++ b/Terminal.Gui/Drivers/SizeMonitorImpl.cs @@ -1,5 +1,4 @@ -#nullable enable -using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging; namespace Terminal.Gui.Drivers; diff --git a/Terminal.Gui/Drivers/UnixDriver/UnixClipboard.cs b/Terminal.Gui/Drivers/UnixDriver/UnixClipboard.cs index c562ee66d..6562f3b2d 100644 --- a/Terminal.Gui/Drivers/UnixDriver/UnixClipboard.cs +++ b/Terminal.Gui/Drivers/UnixDriver/UnixClipboard.cs @@ -1,3 +1,4 @@ +#nullable disable using System.Runtime.InteropServices; namespace Terminal.Gui.Drivers; diff --git a/Terminal.Gui/Drivers/UnixDriver/UnixComponentFactory.cs b/Terminal.Gui/Drivers/UnixDriver/UnixComponentFactory.cs index f53b88f85..5067832d7 100644 --- a/Terminal.Gui/Drivers/UnixDriver/UnixComponentFactory.cs +++ b/Terminal.Gui/Drivers/UnixDriver/UnixComponentFactory.cs @@ -1,4 +1,3 @@ -#nullable enable using System.Collections.Concurrent; namespace Terminal.Gui.Drivers; diff --git a/Terminal.Gui/Drivers/UnixDriver/UnixInput.cs b/Terminal.Gui/Drivers/UnixDriver/UnixInput.cs index 0337e0aff..60807d5c4 100644 --- a/Terminal.Gui/Drivers/UnixDriver/UnixInput.cs +++ b/Terminal.Gui/Drivers/UnixDriver/UnixInput.cs @@ -1,4 +1,3 @@ -#nullable enable using System.Runtime.InteropServices; // ReSharper disable IdentifierTypo diff --git a/Terminal.Gui/Drivers/UnixDriver/UnixKeyConverter.cs b/Terminal.Gui/Drivers/UnixDriver/UnixKeyConverter.cs index 62c05a0ed..22f95d4d3 100644 --- a/Terminal.Gui/Drivers/UnixDriver/UnixKeyConverter.cs +++ b/Terminal.Gui/Drivers/UnixDriver/UnixKeyConverter.cs @@ -1,5 +1,3 @@ -#nullable enable - namespace Terminal.Gui.Drivers; /// diff --git a/Terminal.Gui/Drivers/UnixDriver/UnixOutput.cs b/Terminal.Gui/Drivers/UnixDriver/UnixOutput.cs index 17dbe2bb4..dfbf63ead 100644 --- a/Terminal.Gui/Drivers/UnixDriver/UnixOutput.cs +++ b/Terminal.Gui/Drivers/UnixDriver/UnixOutput.cs @@ -1,4 +1,4 @@ -#nullable enable + using System.Runtime.InteropServices; using Microsoft.Win32.SafeHandles; diff --git a/Terminal.Gui/Drivers/WindowsDriver/ClipboardImpl.cs b/Terminal.Gui/Drivers/WindowsDriver/ClipboardImpl.cs index 6dc708110..cab68fc74 100644 --- a/Terminal.Gui/Drivers/WindowsDriver/ClipboardImpl.cs +++ b/Terminal.Gui/Drivers/WindowsDriver/ClipboardImpl.cs @@ -1,4 +1,3 @@ -#nullable enable using System.ComponentModel; using System.Runtime.InteropServices; diff --git a/Terminal.Gui/Drivers/WindowsDriver/WindowsComponentFactory.cs b/Terminal.Gui/Drivers/WindowsDriver/WindowsComponentFactory.cs index 1f16fd66a..4c361b00d 100644 --- a/Terminal.Gui/Drivers/WindowsDriver/WindowsComponentFactory.cs +++ b/Terminal.Gui/Drivers/WindowsDriver/WindowsComponentFactory.cs @@ -1,4 +1,3 @@ -#nullable enable using System.Collections.Concurrent; namespace Terminal.Gui.Drivers; diff --git a/Terminal.Gui/Drivers/WindowsDriver/WindowsConsole.cs b/Terminal.Gui/Drivers/WindowsDriver/WindowsConsole.cs index 744c32588..fa191262e 100644 --- a/Terminal.Gui/Drivers/WindowsDriver/WindowsConsole.cs +++ b/Terminal.Gui/Drivers/WindowsDriver/WindowsConsole.cs @@ -1,4 +1,3 @@ -#nullable enable using System.Runtime.InteropServices; // ReSharper disable InconsistentNaming diff --git a/Terminal.Gui/Drivers/WindowsDriver/WindowsInput.cs b/Terminal.Gui/Drivers/WindowsDriver/WindowsInput.cs index 026e1f61b..dc3c98205 100644 --- a/Terminal.Gui/Drivers/WindowsDriver/WindowsInput.cs +++ b/Terminal.Gui/Drivers/WindowsDriver/WindowsInput.cs @@ -1,4 +1,3 @@ -#nullable enable using System.Runtime.InteropServices; using Microsoft.Extensions.Logging; using static Terminal.Gui.Drivers.WindowsConsole; diff --git a/Terminal.Gui/Drivers/WindowsDriver/WindowsInputProcessor.cs b/Terminal.Gui/Drivers/WindowsDriver/WindowsInputProcessor.cs index 028eb4dc1..739a393fb 100644 --- a/Terminal.Gui/Drivers/WindowsDriver/WindowsInputProcessor.cs +++ b/Terminal.Gui/Drivers/WindowsDriver/WindowsInputProcessor.cs @@ -1,4 +1,3 @@ -#nullable enable using System.Collections.Concurrent; namespace Terminal.Gui.Drivers; diff --git a/Terminal.Gui/Drivers/WindowsDriver/WindowsKeyConverter.cs b/Terminal.Gui/Drivers/WindowsDriver/WindowsKeyConverter.cs index 4458ad6ff..0a8f2427f 100644 --- a/Terminal.Gui/Drivers/WindowsDriver/WindowsKeyConverter.cs +++ b/Terminal.Gui/Drivers/WindowsDriver/WindowsKeyConverter.cs @@ -1,5 +1,3 @@ -#nullable enable - namespace Terminal.Gui.Drivers; /// diff --git a/Terminal.Gui/Drivers/WindowsDriver/WindowsKeyHelper.cs b/Terminal.Gui/Drivers/WindowsDriver/WindowsKeyHelper.cs index 56193db21..cd424136e 100644 --- a/Terminal.Gui/Drivers/WindowsDriver/WindowsKeyHelper.cs +++ b/Terminal.Gui/Drivers/WindowsDriver/WindowsKeyHelper.cs @@ -1,4 +1,3 @@ -#nullable enable using System.Diagnostics; // ReSharper disable InconsistentNaming diff --git a/Terminal.Gui/Drivers/WindowsDriver/WindowsKeyboardLayout.cs b/Terminal.Gui/Drivers/WindowsDriver/WindowsKeyboardLayout.cs index e368dfa8d..38fc85bfe 100644 --- a/Terminal.Gui/Drivers/WindowsDriver/WindowsKeyboardLayout.cs +++ b/Terminal.Gui/Drivers/WindowsDriver/WindowsKeyboardLayout.cs @@ -1,3 +1,4 @@ +#nullable disable using System.Runtime.InteropServices; namespace Terminal.Gui.Drivers; diff --git a/Terminal.Gui/Drivers/WindowsDriver/WindowsOutput.cs b/Terminal.Gui/Drivers/WindowsDriver/WindowsOutput.cs index a0bea436c..5a81ae0ab 100644 --- a/Terminal.Gui/Drivers/WindowsDriver/WindowsOutput.cs +++ b/Terminal.Gui/Drivers/WindowsDriver/WindowsOutput.cs @@ -1,4 +1,3 @@ -#nullable enable using System.ComponentModel; using System.Runtime.InteropServices; using Microsoft.Extensions.Logging; @@ -148,7 +147,9 @@ internal partial class WindowsOutput : OutputBase, IOutput } // Force 16 colors if not in virtual terminal mode. - Application.Force16Colors = true; + // BUGBUG: This is bad. It does not work if the app was crated without + // BUGBUG: Apis. + ApplicationImpl.Instance.Force16Colors = true; } @@ -263,7 +264,10 @@ internal partial class WindowsOutput : OutputBase, IOutput public override void Write (IOutputBuffer outputBuffer) { - _force16Colors = Application.Driver!.Force16Colors; + // BUGBUG: This is bad. It does not work if the app was crated without + // BUGBUG: Apis. + //_force16Colors = ApplicationImpl.Instance.Driver!.Force16Colors; + _force16Colors = false; _everythingStringBuilder.Clear (); // for 16 color mode we will write to a backing buffer then flip it to the active one at the end to avoid jitter. @@ -303,6 +307,12 @@ internal partial class WindowsOutput : OutputBase, IOutput { int err = Marshal.GetLastWin32Error (); + if (err == 1) + { + Logging.Logger.LogError ($"Error: {Marshal.GetLastWin32Error ()} in {nameof (WindowsOutput)}"); + + return; + } if (err != 0) { throw new Win32Exception (err); @@ -345,7 +355,9 @@ internal partial class WindowsOutput : OutputBase, IOutput /// protected override void AppendOrWriteAttribute (StringBuilder output, Attribute attr, TextStyle redrawTextStyle) { - bool force16Colors = Application.Force16Colors; + // BUGBUG: This is bad. It does not work if the app was crated without + // BUGBUG: Apis. + bool force16Colors = ApplicationImpl.Instance.Force16Colors; if (force16Colors) { diff --git a/Terminal.Gui/FileServices/DefaultSearchMatcher.cs b/Terminal.Gui/FileServices/DefaultSearchMatcher.cs index 3c10e2a1c..3d9ce8046 100644 --- a/Terminal.Gui/FileServices/DefaultSearchMatcher.cs +++ b/Terminal.Gui/FileServices/DefaultSearchMatcher.cs @@ -4,8 +4,8 @@ namespace Terminal.Gui.FileServices; internal class DefaultSearchMatcher : ISearchMatcher { - private string [] terms; - public void Initialize (string terms) { this.terms = terms.Split (new [] { " " }, StringSplitOptions.RemoveEmptyEntries); } + private string []? _terms; + public void Initialize (string terms) { _terms = terms.Split ([" "], StringSplitOptions.RemoveEmptyEntries); } public bool IsMatch (IFileSystemInfo f) { @@ -15,10 +15,10 @@ internal class DefaultSearchMatcher : ISearchMatcher return // At least one term must match the file name only e.g. "my" in "myfile.csv" - terms.Any (t => f.Name.IndexOf (t, StringComparison.OrdinalIgnoreCase) >= 0) + _terms!.Any (t => f.Name.IndexOf (t, StringComparison.OrdinalIgnoreCase) >= 0) && // All terms must exist in full path e.g. "dos my" can match "c:\documents\myfile.csv" - terms.All (t => f.FullName.IndexOf (t, StringComparison.OrdinalIgnoreCase) >= 0); + _terms!.All (t => f.FullName.IndexOf (t, StringComparison.OrdinalIgnoreCase) >= 0); } } diff --git a/Terminal.Gui/FileServices/FileSystemIconProvider.cs b/Terminal.Gui/FileServices/FileSystemIconProvider.cs index 607582576..de76d91c6 100644 --- a/Terminal.Gui/FileServices/FileSystemIconProvider.cs +++ b/Terminal.Gui/FileServices/FileSystemIconProvider.cs @@ -1,4 +1,3 @@ -#nullable enable using System.IO.Abstractions; namespace Terminal.Gui.FileServices; diff --git a/Terminal.Gui/FileServices/FileSystemInfoStats.cs b/Terminal.Gui/FileServices/FileSystemInfoStats.cs index 72f8ded2d..5444aa26f 100644 --- a/Terminal.Gui/FileServices/FileSystemInfoStats.cs +++ b/Terminal.Gui/FileServices/FileSystemInfoStats.cs @@ -1,5 +1,4 @@ -#nullable enable -using System.Globalization; +using System.Globalization; using System.IO.Abstractions; namespace Terminal.Gui.FileServices; diff --git a/Terminal.Gui/FileServices/FileSystemTreeBuilder.cs b/Terminal.Gui/FileServices/FileSystemTreeBuilder.cs index 87aba39af..a44ddd049 100644 --- a/Terminal.Gui/FileServices/FileSystemTreeBuilder.cs +++ b/Terminal.Gui/FileServices/FileSystemTreeBuilder.cs @@ -1,4 +1,5 @@ -using System.IO.Abstractions; +#nullable disable +using System.IO.Abstractions; namespace Terminal.Gui.FileServices; diff --git a/Terminal.Gui/Input/Command.cs b/Terminal.Gui/Input/Command.cs index 88d99e639..4d1b6d008 100644 --- a/Terminal.Gui/Input/Command.cs +++ b/Terminal.Gui/Input/Command.cs @@ -336,4 +336,4 @@ public enum Command Edit, #endregion -} \ No newline at end of file +} diff --git a/Terminal.Gui/Input/CommandContext.cs b/Terminal.Gui/Input/CommandContext.cs index fe6c9c194..dd6c529ba 100644 --- a/Terminal.Gui/Input/CommandContext.cs +++ b/Terminal.Gui/Input/CommandContext.cs @@ -1,5 +1,4 @@ -#nullable enable - + namespace Terminal.Gui.Input; #pragma warning disable CS1574, CS0419 // XML comment has cref attribute that could not be resolved @@ -33,4 +32,4 @@ public record struct CommandContext : ICommandContext /// The keyboard or mouse minding that was used to invoke the , if any. /// public TBinding? Binding { get; set; } -} \ No newline at end of file +} diff --git a/Terminal.Gui/Input/CommandEventArgs.cs b/Terminal.Gui/Input/CommandEventArgs.cs index 05d662437..659d0db64 100644 --- a/Terminal.Gui/Input/CommandEventArgs.cs +++ b/Terminal.Gui/Input/CommandEventArgs.cs @@ -1,5 +1,4 @@ -#nullable enable -using System.ComponentModel; +using System.ComponentModel; namespace Terminal.Gui.Input; diff --git a/Terminal.Gui/Input/ICommandContext.cs b/Terminal.Gui/Input/ICommandContext.cs index a9d4e641c..c4e57de22 100644 --- a/Terminal.Gui/Input/ICommandContext.cs +++ b/Terminal.Gui/Input/ICommandContext.cs @@ -1,5 +1,4 @@ -#nullable enable - + namespace Terminal.Gui.Input; #pragma warning disable CS1574 // XML comment has cref attribute that could not be resolved diff --git a/Terminal.Gui/Input/IInputBinding.cs b/Terminal.Gui/Input/IInputBinding.cs index f0ae3a25a..34170c73c 100644 --- a/Terminal.Gui/Input/IInputBinding.cs +++ b/Terminal.Gui/Input/IInputBinding.cs @@ -1,5 +1,4 @@ -#nullable enable -namespace Terminal.Gui.Input; +namespace Terminal.Gui.Input; /// /// Describes an input binding. Used to bind a set of objects to a specific input event. diff --git a/Terminal.Gui/Input/InputBindings.cs b/Terminal.Gui/Input/InputBindings.cs index 75a4711e5..8711cf87c 100644 --- a/Terminal.Gui/Input/InputBindings.cs +++ b/Terminal.Gui/Input/InputBindings.cs @@ -1,5 +1,4 @@ -#nullable enable -namespace Terminal.Gui.Input; +namespace Terminal.Gui.Input; /// /// Abstract class for and . diff --git a/Terminal.Gui/Input/Keyboard/Key.cs b/Terminal.Gui/Input/Keyboard/Key.cs index cd9924047..7a60b6cb3 100644 --- a/Terminal.Gui/Input/Keyboard/Key.cs +++ b/Terminal.Gui/Input/Keyboard/Key.cs @@ -1,4 +1,3 @@ -#nullable enable using System.Diagnostics.CodeAnalysis; using System.Globalization; diff --git a/Terminal.Gui/Input/Keyboard/KeyBinding.cs b/Terminal.Gui/Input/Keyboard/KeyBinding.cs index b55a66842..6d97a6b6e 100644 --- a/Terminal.Gui/Input/Keyboard/KeyBinding.cs +++ b/Terminal.Gui/Input/Keyboard/KeyBinding.cs @@ -1,4 +1,4 @@ -#nullable enable + // These classes use a key binding system based on the design implemented in Scintilla.Net which is an diff --git a/Terminal.Gui/Input/Keyboard/KeyBindings.cs b/Terminal.Gui/Input/Keyboard/KeyBindings.cs index c4a2952eb..46a7081de 100644 --- a/Terminal.Gui/Input/Keyboard/KeyBindings.cs +++ b/Terminal.Gui/Input/Keyboard/KeyBindings.cs @@ -1,4 +1,4 @@ -#nullable enable + namespace Terminal.Gui.Input; diff --git a/Terminal.Gui/Input/Keyboard/KeyEqualityComparer.cs b/Terminal.Gui/Input/Keyboard/KeyEqualityComparer.cs index 287dd9cfa..25841107d 100644 --- a/Terminal.Gui/Input/Keyboard/KeyEqualityComparer.cs +++ b/Terminal.Gui/Input/Keyboard/KeyEqualityComparer.cs @@ -1,4 +1,3 @@ -#nullable enable /// /// diff --git a/Terminal.Gui/Input/Mouse/GrabMouseEventArgs.cs b/Terminal.Gui/Input/Mouse/GrabMouseEventArgs.cs index c05fd7471..fd3ae5654 100644 --- a/Terminal.Gui/Input/Mouse/GrabMouseEventArgs.cs +++ b/Terminal.Gui/Input/Mouse/GrabMouseEventArgs.cs @@ -1,4 +1,3 @@ - namespace Terminal.Gui.Input; /// Args GrabMouse related events. diff --git a/Terminal.Gui/Input/Mouse/MouseBinding.cs b/Terminal.Gui/Input/Mouse/MouseBinding.cs index 1c6ebf386..40f32ea8f 100644 --- a/Terminal.Gui/Input/Mouse/MouseBinding.cs +++ b/Terminal.Gui/Input/Mouse/MouseBinding.cs @@ -1,4 +1,4 @@ -#nullable enable + namespace Terminal.Gui.Input; diff --git a/Terminal.Gui/Input/Mouse/MouseBindings.cs b/Terminal.Gui/Input/Mouse/MouseBindings.cs index 55d0bf61d..255be246b 100644 --- a/Terminal.Gui/Input/Mouse/MouseBindings.cs +++ b/Terminal.Gui/Input/Mouse/MouseBindings.cs @@ -1,4 +1,4 @@ -#nullable enable + namespace Terminal.Gui.Input; /// diff --git a/Terminal.Gui/Input/Mouse/MouseEventArgs.cs b/Terminal.Gui/Input/Mouse/MouseEventArgs.cs index db0008ef3..3e10c68a4 100644 --- a/Terminal.Gui/Input/Mouse/MouseEventArgs.cs +++ b/Terminal.Gui/Input/Mouse/MouseEventArgs.cs @@ -1,4 +1,4 @@ -#nullable enable + using System.ComponentModel; namespace Terminal.Gui.Input; diff --git a/Terminal.Gui/Resources/GlobalResources.cs b/Terminal.Gui/Resources/GlobalResources.cs index 8a9ac5a0c..4c05e3415 100644 --- a/Terminal.Gui/Resources/GlobalResources.cs +++ b/Terminal.Gui/Resources/GlobalResources.cs @@ -1,5 +1,4 @@ -#nullable enable - + using System.Collections; using System.Globalization; using System.Resources; diff --git a/Terminal.Gui/Resources/ResourceManagerWrapper.cs b/Terminal.Gui/Resources/ResourceManagerWrapper.cs index 8bcc9271f..dc3651454 100644 --- a/Terminal.Gui/Resources/ResourceManagerWrapper.cs +++ b/Terminal.Gui/Resources/ResourceManagerWrapper.cs @@ -1,5 +1,4 @@ -#nullable enable - + using System.Collections; using System.Globalization; using System.Resources; diff --git a/Terminal.Gui/Terminal.Gui.csproj b/Terminal.Gui/Terminal.Gui.csproj index 15d586975..6f1e53802 100644 --- a/Terminal.Gui/Terminal.Gui.csproj +++ b/Terminal.Gui/Terminal.Gui.csproj @@ -24,6 +24,7 @@ net8.0 12 enable + enable $(AssemblyName) true @@ -163,6 +164,16 @@ + + + + + + + + + + diff --git a/Terminal.Gui/Text/NerdFonts.cs b/Terminal.Gui/Text/NerdFonts.cs index 2ceaf4fa7..c6da80697 100644 --- a/Terminal.Gui/Text/NerdFonts.cs +++ b/Terminal.Gui/Text/NerdFonts.cs @@ -741,8 +741,13 @@ internal class NerdFonts { "nf-seti-typescript", '' } }; - public char GetNerdIcon (IFileSystemInfo file, bool isOpen) + public char GetNerdIcon (IFileSystemInfo? file, bool isOpen) { + if (file == null) + { + throw new ArgumentNullException (nameof (file)); + } + if (FilenameToIcon.ContainsKey (file.Name)) { return Glyphs [FilenameToIcon [file.Name]]; diff --git a/Terminal.Gui/Text/RuneExtensions.cs b/Terminal.Gui/Text/RuneExtensions.cs index 4dfb4bb3f..c8a273c83 100644 --- a/Terminal.Gui/Text/RuneExtensions.cs +++ b/Terminal.Gui/Text/RuneExtensions.cs @@ -1,5 +1,4 @@ -#nullable enable - + using System.Globalization; using Wcwidth; diff --git a/Terminal.Gui/Text/StringExtensions.cs b/Terminal.Gui/Text/StringExtensions.cs index e379cf3da..eb053a3d8 100644 --- a/Terminal.Gui/Text/StringExtensions.cs +++ b/Terminal.Gui/Text/StringExtensions.cs @@ -1,5 +1,4 @@ -#nullable enable -using System.Buffers; +using System.Buffers; using System.Globalization; namespace Terminal.Gui.Text; diff --git a/Terminal.Gui/Text/TextDirection.cs b/Terminal.Gui/Text/TextDirection.cs index 2ea3ba3a4..b989c7337 100644 --- a/Terminal.Gui/Text/TextDirection.cs +++ b/Terminal.Gui/Text/TextDirection.cs @@ -58,4 +58,4 @@ public enum TextDirection /// This is a vertical direction. D O
L L
R L
O E
W H
BottomTop_RightLeft -} \ No newline at end of file +} diff --git a/Terminal.Gui/Text/TextFormatter.cs b/Terminal.Gui/Text/TextFormatter.cs index 70636c018..a11289a2b 100644 --- a/Terminal.Gui/Text/TextFormatter.cs +++ b/Terminal.Gui/Text/TextFormatter.cs @@ -1,4 +1,3 @@ -#nullable enable using System.Buffers; using System.Diagnostics; @@ -11,7 +10,7 @@ namespace Terminal.Gui.Text; public class TextFormatter { // Utilized in CRLF related helper methods for faster newline char index search. - private static readonly SearchValues NewlineSearchValues = SearchValues.Create(['\r', '\n']); + private static readonly SearchValues NewlineSearchValues = SearchValues.Create (['\r', '\n']); private Key _hotKey = new (); private int _hotKeyPos = -1; @@ -52,31 +51,28 @@ public class TextFormatter /// Causes the text to be formatted (references ). Sets to /// false. /// + /// The console driver currently used by the application. /// Specifies the screen-relative location and maximum size for drawing the text. /// The color to use for all text except the hotkey /// The color to use to draw the hotkey /// Specifies the screen-relative location and maximum container size. - /// The console driver currently used by the application. /// public void Draw ( + IDriver? driver, Rectangle screen, Attribute normalColor, Attribute hotColor, - Rectangle maximum = default, - IDriver? driver = null + Rectangle maximum = default ) { + ArgumentNullException.ThrowIfNull (driver); + // With this check, we protect against subclasses with overrides of Text (like Button) if (string.IsNullOrEmpty (Text)) { return; } - if (driver is null) - { - driver = Application.Driver; - } - driver?.SetAttribute (normalColor); List linesFormatted = GetLines (); @@ -1199,8 +1195,8 @@ public class TextFormatter return str; } - StringBuilder stringBuilder = new(); - ReadOnlySpan firstSegment = remaining[..firstNewlineCharIndex]; + StringBuilder stringBuilder = new (); + ReadOnlySpan firstSegment = remaining [..firstNewlineCharIndex]; stringBuilder.Append (firstSegment); // The first newline is not yet skipped because the "keepNewLine" condition has not been evaluated. @@ -1215,7 +1211,7 @@ public class TextFormatter break; } - ReadOnlySpan segment = remaining[..newlineCharIndex]; + ReadOnlySpan segment = remaining [..newlineCharIndex]; stringBuilder.Append (segment); int stride = segment.Length; @@ -1267,8 +1263,8 @@ public class TextFormatter return str; } - StringBuilder stringBuilder = new(); - ReadOnlySpan firstSegment = remaining[..firstNewlineCharIndex]; + StringBuilder stringBuilder = new (); + ReadOnlySpan firstSegment = remaining [..firstNewlineCharIndex]; stringBuilder.Append (firstSegment); // The first newline is not yet skipped because the newline type has not been evaluated. @@ -1283,7 +1279,7 @@ public class TextFormatter break; } - ReadOnlySpan segment = remaining[..newlineCharIndex]; + ReadOnlySpan segment = remaining [..newlineCharIndex]; stringBuilder.Append (segment); int stride = segment.Length; @@ -2445,12 +2441,12 @@ public class TextFormatter } const int maxStackallocCharBufferSize = 512; // ~1 kB - char[]? rentedBufferArray = null; + char []? rentedBufferArray = null; try { Span buffer = text.Length <= maxStackallocCharBufferSize - ? stackalloc char[text.Length] - : (rentedBufferArray = ArrayPool.Shared.Rent(text.Length)); + ? stackalloc char [text.Length] + : (rentedBufferArray = ArrayPool.Shared.Rent (text.Length)); int i = 0; var remainingBuffer = buffer; @@ -2468,7 +2464,7 @@ public class TextFormatter ReadOnlySpan newText = buffer [..^remainingBuffer.Length]; // If the resulting string would be the same as original then just return the original. - if (newText.Equals(text, StringComparison.Ordinal)) + if (newText.Equals (text, StringComparison.Ordinal)) { return text; } diff --git a/Terminal.Gui/ViewBase/Adornment/Adornment.cs b/Terminal.Gui/ViewBase/Adornment/Adornment.cs index 9d5675b6b..6852efea4 100644 --- a/Terminal.Gui/ViewBase/Adornment/Adornment.cs +++ b/Terminal.Gui/ViewBase/Adornment/Adornment.cs @@ -1,4 +1,4 @@ -#nullable enable + namespace Terminal.Gui.ViewBase; @@ -84,6 +84,12 @@ public class Adornment : View, IDesignable #region View Overrides + /// + protected override IApplication? GetApp () => Parent?.App; + + /// + protected override IDriver? GetDriver () => Parent?.Driver ?? base.GetDriver(); + // If a scheme is explicitly set, use that. Otherwise, use the scheme of the parent view. private Scheme? _scheme; @@ -176,7 +182,10 @@ public class Adornment : View, IDesignable } // This just draws/clears the thickness, not the insides. - Thickness.Draw (ViewportToScreen (Viewport), Diagnostics, ToString ()); + if (Driver is { }) + { + Thickness.Draw (Driver, ViewportToScreen (Viewport), Diagnostics, ToString ()); + } NeedsDraw = true; diff --git a/Terminal.Gui/ViewBase/Adornment/Border.Arrangment.cs b/Terminal.Gui/ViewBase/Adornment/Border.Arrangment.cs index b55fec027..f2e52fab8 100644 --- a/Terminal.Gui/ViewBase/Adornment/Border.Arrangment.cs +++ b/Terminal.Gui/ViewBase/Adornment/Border.Arrangment.cs @@ -1,4 +1,3 @@ -#nullable enable using System.ComponentModel; using System.Diagnostics; @@ -40,7 +39,10 @@ public partial class Border // Add Commands and KeyBindings - Note it's ok these get added each time. KeyBindings are cleared in EndArrange() AddArrangeModeKeyBindings (); - Application.MouseEvent += ApplicationOnMouseEvent; + if (App is { }) + { + App.Mouse.MouseEvent += ApplicationOnMouseEvent; + } // Create all necessary arrangement buttons CreateArrangementButtons (); @@ -429,11 +431,14 @@ public partial class Border MouseState &= ~MouseState.Pressed; - Application.MouseEvent -= ApplicationOnMouseEvent; - - if (Application.Mouse.MouseGrabView == this && _dragPosition.HasValue) + if (App is { }) { - Application.Mouse.UngrabMouse (); + App.Mouse.MouseEvent -= ApplicationOnMouseEvent; + + if (App.Mouse.MouseGrabView == this && _dragPosition.HasValue) + { + App.Mouse.UngrabMouse (); + } } // Clean up all arrangement buttons @@ -498,7 +503,7 @@ public partial class Border // Set the start grab point to the Frame coords _startGrabPoint = new (mouseEvent.Position.X + Frame.X, mouseEvent.Position.Y + Frame.Y); _dragPosition = mouseEvent.Position; - Application.Mouse.GrabMouse (this); + App?.Mouse.GrabMouse (this); // Determine the mode based on where the click occurred ViewArrangement arrangeMode = DetermineArrangeModeFromClick (); @@ -511,7 +516,7 @@ public partial class Border return true; } - if (mouseEvent.Flags is (MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition) && Application.Mouse.MouseGrabView == this) + if (mouseEvent.Flags is (MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition) && App?.Mouse.MouseGrabView == this) { if (_dragPosition.HasValue) { @@ -523,7 +528,7 @@ public partial class Border if (mouseEvent.Flags.HasFlag (MouseFlags.Button1Released) && _dragPosition.HasValue) { _dragPosition = null; - Application.Mouse.UngrabMouse (); + App?.Mouse.UngrabMouse (); EndArrangeMode (); @@ -652,7 +657,7 @@ public partial class Border if (Parent!.SuperView is null) { // Redraw the entire app window. - Application.Top!.SetNeedsDraw (); + App?.Current?.SetNeedsDraw (); } else { @@ -763,7 +768,7 @@ public partial class Border private void Application_GrabbingMouse (object? sender, GrabMouseEventArgs e) { - if (Application.Mouse.MouseGrabView == this && _dragPosition.HasValue) + if (App?.Mouse.MouseGrabView == this && _dragPosition.HasValue) { e.Cancel = true; } @@ -771,7 +776,7 @@ public partial class Border private void Application_UnGrabbingMouse (object? sender, GrabMouseEventArgs e) { - if (Application.Mouse.MouseGrabView == this && _dragPosition.HasValue) + if (App?.Mouse.MouseGrabView == this && _dragPosition.HasValue) { e.Cancel = true; } @@ -784,8 +789,11 @@ public partial class Border /// protected override void Dispose (bool disposing) { - Application.Mouse.GrabbingMouse -= Application_GrabbingMouse; - Application.Mouse.UnGrabbingMouse -= Application_UnGrabbingMouse; + if (App is { }) + { + App.Mouse.GrabbingMouse -= Application_GrabbingMouse; + App.Mouse.UnGrabbingMouse -= Application_UnGrabbingMouse; + } _dragPosition = null; base.Dispose (disposing); diff --git a/Terminal.Gui/ViewBase/Adornment/Border.cs b/Terminal.Gui/ViewBase/Adornment/Border.cs index 18a99b1c1..bfbe92a08 100644 --- a/Terminal.Gui/ViewBase/Adornment/Border.cs +++ b/Terminal.Gui/ViewBase/Adornment/Border.cs @@ -1,4 +1,3 @@ -#nullable enable using System.Diagnostics; namespace Terminal.Gui.ViewBase; @@ -49,10 +48,6 @@ public partial class Border : Adornment Parent = parent; CanFocus = false; TabStop = TabBehavior.TabGroup; - - Application.Mouse.GrabbingMouse += Application_GrabbingMouse; - Application.Mouse.UnGrabbingMouse += Application_UnGrabbingMouse; - ThicknessChanged += OnThicknessChanged; } @@ -113,6 +108,12 @@ public partial class Border : Adornment { base.BeginInit (); + if (App is { }) + { + App.Mouse.GrabbingMouse += Application_GrabbingMouse; + App.Mouse.UnGrabbingMouse += Application_UnGrabbingMouse; + } + if (Parent is null) { return; @@ -312,7 +313,8 @@ public partial class Border : Adornment } } - if (Parent is { } + if (Driver is { } + && Parent is { } && canDrawBorder && Thickness.Top > 0 && maxTitleWidth > 0 @@ -321,10 +323,7 @@ public partial class Border : Adornment { Rectangle titleRect = new (borderBounds.X + 2, titleY, maxTitleWidth, 1); - Parent.TitleTextFormatter.Draw ( - titleRect, - GetAttributeForRole (Parent.HasFocus ? VisualRole.Focus : VisualRole.Normal), - GetAttributeForRole (Parent.HasFocus ? VisualRole.HotFocus : VisualRole.HotNormal)); + Parent.TitleTextFormatter.Draw (driver: Driver, screen: titleRect, normalColor: GetAttributeForRole (Parent.HasFocus ? VisualRole.Focus : VisualRole.Normal), hotColor: GetAttributeForRole (Parent.HasFocus ? VisualRole.HotFocus : VisualRole.HotNormal)); Parent?.LineCanvas.Exclude (new (titleRect)); } @@ -498,16 +497,13 @@ public partial class Border : Adornment if (drawTop) { - hruler.Draw (new (screenBounds.X, screenBounds.Y)); + hruler.Draw (driver: Driver, location: new (screenBounds.X, screenBounds.Y)); } // Redraw title if (drawTop && maxTitleWidth > 0 && Settings.FastHasFlags (BorderSettings.Title)) { - Parent!.TitleTextFormatter.Draw ( - new (borderBounds.X + 2, titleY, maxTitleWidth, 1), - Parent.HasFocus ? Parent.GetAttributeForRole (VisualRole.Focus) : Parent.GetAttributeForRole (VisualRole.Normal), - Parent.HasFocus ? Parent.GetAttributeForRole (VisualRole.Focus) : Parent.GetAttributeForRole (VisualRole.Normal)); + Parent!.TitleTextFormatter.Draw (driver: Driver, screen: new (borderBounds.X + 2, titleY, maxTitleWidth, 1), normalColor: Parent.HasFocus ? Parent.GetAttributeForRole (VisualRole.Focus) : Parent.GetAttributeForRole (VisualRole.Normal), hotColor: Parent.HasFocus ? Parent.GetAttributeForRole (VisualRole.Focus) : Parent.GetAttributeForRole (VisualRole.Normal)); } //Left @@ -515,19 +511,19 @@ public partial class Border : Adornment if (drawLeft) { - vruler.Draw (new (screenBounds.X, screenBounds.Y + 1), 1); + vruler.Draw (driver: Driver, location: new (screenBounds.X, screenBounds.Y + 1), start: 1); } // Bottom if (drawBottom) { - hruler.Draw (new (screenBounds.X, screenBounds.Y + screenBounds.Height - 1)); + hruler.Draw (driver: Driver, location: new (screenBounds.X, screenBounds.Y + screenBounds.Height - 1)); } // Right if (drawRight) { - vruler.Draw (new (screenBounds.X + screenBounds.Width - 1, screenBounds.Y + 1), 1); + vruler.Draw (driver: Driver, location: new (screenBounds.X + screenBounds.Width - 1, screenBounds.Y + 1), start: 1); } } diff --git a/Terminal.Gui/ViewBase/Adornment/BorderSettings.cs b/Terminal.Gui/ViewBase/Adornment/BorderSettings.cs index 516842000..4921446ae 100644 --- a/Terminal.Gui/ViewBase/Adornment/BorderSettings.cs +++ b/Terminal.Gui/ViewBase/Adornment/BorderSettings.cs @@ -1,12 +1,9 @@ - - namespace Terminal.Gui.ViewBase; /// /// Determines the settings for . /// [Flags] - public enum BorderSettings { /// diff --git a/Terminal.Gui/ViewBase/Adornment/Margin.cs b/Terminal.Gui/ViewBase/Adornment/Margin.cs index 59b39930f..0ce7740ba 100644 --- a/Terminal.Gui/ViewBase/Adornment/Margin.cs +++ b/Terminal.Gui/ViewBase/Adornment/Margin.cs @@ -1,4 +1,4 @@ -#nullable enable + using System.Runtime.InteropServices; @@ -80,10 +80,10 @@ public class Margin : Adornment if (view.Margin?.GetCachedClip () != null) { view.Margin!.NeedsDraw = true; - Region? saved = GetClip (); - View.SetClip (view.Margin!.GetCachedClip ()); - view.Margin!.Draw (); - View.SetClip (saved); + Region? saved = view.GetClip (); + view.SetClip (view.Margin!.GetCachedClip ()); + view.Margin!.Draw (); + view.SetClip (saved); view.Margin!.ClearCachedClip (); } @@ -128,7 +128,7 @@ public class Margin : Adornment // This just draws/clears the thickness, not the insides. // TODO: This is a hack. See https://github.com/gui-cs/Terminal.Gui/issues/4016 //SetAttribute (GetAttributeForRole (VisualRole.Normal)); - Thickness.Draw (screen, Diagnostics, ToString ()); + Thickness.Draw (Driver, screen, Diagnostics, ToString ()); } if (ShadowStyle != ShadowStyle.None) diff --git a/Terminal.Gui/ViewBase/Adornment/Padding.cs b/Terminal.Gui/ViewBase/Adornment/Padding.cs index 508670504..0f19073a4 100644 --- a/Terminal.Gui/ViewBase/Adornment/Padding.cs +++ b/Terminal.Gui/ViewBase/Adornment/Padding.cs @@ -1,4 +1,4 @@ -#nullable enable + namespace Terminal.Gui.ViewBase; diff --git a/Terminal.Gui/ViewBase/Adornment/ShadowView.cs b/Terminal.Gui/ViewBase/Adornment/ShadowView.cs index 151aa149c..9212e7271 100644 --- a/Terminal.Gui/ViewBase/Adornment/ShadowView.cs +++ b/Terminal.Gui/ViewBase/Adornment/ShadowView.cs @@ -1,4 +1,4 @@ -#nullable enable + namespace Terminal.Gui.ViewBase; @@ -144,9 +144,9 @@ internal class ShadowView : View { if (SuperView is not Adornment adornment || location.X < 0 - || location.X >= Application.Screen.Width + || location.X >= App?.Screen.Width || location.Y < 0 - || location.Y >= Application.Screen.Height) + || location.Y >= App?.Screen.Height) { return Attribute.Default; } @@ -170,7 +170,7 @@ internal class ShadowView : View // use the Normal attribute from the View under the shadow. if (newAttribute.Background == Color.DarkGray) { - List currentViewsUnderMouse = View.GetViewsUnderLocation (location, ViewportSettingsFlags.Transparent); + List currentViewsUnderMouse = GetViewsUnderLocation (location, ViewportSettingsFlags.Transparent); View? underView = currentViewsUnderMouse!.LastOrDefault (); attr = underView?.GetAttributeForRole (VisualRole.Normal) ?? Attribute.Default; diff --git a/Terminal.Gui/ViewBase/DrawAdornmentsEventArgs.cs b/Terminal.Gui/ViewBase/DrawAdornmentsEventArgs.cs index 97092cf91..4fc8b826b 100644 --- a/Terminal.Gui/ViewBase/DrawAdornmentsEventArgs.cs +++ b/Terminal.Gui/ViewBase/DrawAdornmentsEventArgs.cs @@ -1,5 +1,4 @@ -#nullable enable -namespace Terminal.Gui.ViewBase; +namespace Terminal.Gui.ViewBase; /// /// Provides data for events that allow cancellation of adornment drawing in the Cancellable Work Pattern (CWP). diff --git a/Terminal.Gui/ViewBase/DrawContext.cs b/Terminal.Gui/ViewBase/DrawContext.cs index 95eec9a2e..e6df3033b 100644 --- a/Terminal.Gui/ViewBase/DrawContext.cs +++ b/Terminal.Gui/ViewBase/DrawContext.cs @@ -1,5 +1,4 @@ -#nullable enable - + namespace Terminal.Gui.ViewBase; /// diff --git a/Terminal.Gui/ViewBase/DrawEventArgs.cs b/Terminal.Gui/ViewBase/DrawEventArgs.cs index f00bdb618..68be3544c 100644 --- a/Terminal.Gui/ViewBase/DrawEventArgs.cs +++ b/Terminal.Gui/ViewBase/DrawEventArgs.cs @@ -1,5 +1,4 @@ -#nullable enable -using System.ComponentModel; +using System.ComponentModel; namespace Terminal.Gui.ViewBase; diff --git a/Terminal.Gui/ViewBase/EnumExtensions/AddOrSubtractExtensions.cs b/Terminal.Gui/ViewBase/EnumExtensions/AddOrSubtractExtensions.cs index eef2b4372..65ee7fd9e 100644 --- a/Terminal.Gui/ViewBase/EnumExtensions/AddOrSubtractExtensions.cs +++ b/Terminal.Gui/ViewBase/EnumExtensions/AddOrSubtractExtensions.cs @@ -1,4 +1,3 @@ -#nullable enable using System.CodeDom.Compiler; using System.Diagnostics; diff --git a/Terminal.Gui/ViewBase/EnumExtensions/AlignmentExtensions.cs b/Terminal.Gui/ViewBase/EnumExtensions/AlignmentExtensions.cs index e94e6b93e..b6c2b05d4 100644 --- a/Terminal.Gui/ViewBase/EnumExtensions/AlignmentExtensions.cs +++ b/Terminal.Gui/ViewBase/EnumExtensions/AlignmentExtensions.cs @@ -1,4 +1,3 @@ -#nullable enable using System.CodeDom.Compiler; using System.Diagnostics; diff --git a/Terminal.Gui/ViewBase/EnumExtensions/AlignmentModesExtensions.cs b/Terminal.Gui/ViewBase/EnumExtensions/AlignmentModesExtensions.cs index da91e93df..b129642f0 100644 --- a/Terminal.Gui/ViewBase/EnumExtensions/AlignmentModesExtensions.cs +++ b/Terminal.Gui/ViewBase/EnumExtensions/AlignmentModesExtensions.cs @@ -1,4 +1,3 @@ -#nullable enable using System.CodeDom.Compiler; using System.Diagnostics; diff --git a/Terminal.Gui/ViewBase/EnumExtensions/BorderSettingsExtensions.cs b/Terminal.Gui/ViewBase/EnumExtensions/BorderSettingsExtensions.cs index b908950d6..c01b5b9b5 100644 --- a/Terminal.Gui/ViewBase/EnumExtensions/BorderSettingsExtensions.cs +++ b/Terminal.Gui/ViewBase/EnumExtensions/BorderSettingsExtensions.cs @@ -1,4 +1,4 @@ -#nullable enable + using System.CodeDom.Compiler; using System.Diagnostics; diff --git a/Terminal.Gui/ViewBase/EnumExtensions/DimAutoStyleExtensions.cs b/Terminal.Gui/ViewBase/EnumExtensions/DimAutoStyleExtensions.cs index edccb473e..3633cc5e8 100644 --- a/Terminal.Gui/ViewBase/EnumExtensions/DimAutoStyleExtensions.cs +++ b/Terminal.Gui/ViewBase/EnumExtensions/DimAutoStyleExtensions.cs @@ -1,4 +1,3 @@ -#nullable enable using System.CodeDom.Compiler; using System.Diagnostics; diff --git a/Terminal.Gui/ViewBase/EnumExtensions/DimPercentModeExtensions.cs b/Terminal.Gui/ViewBase/EnumExtensions/DimPercentModeExtensions.cs index f6a62db75..0eac30890 100644 --- a/Terminal.Gui/ViewBase/EnumExtensions/DimPercentModeExtensions.cs +++ b/Terminal.Gui/ViewBase/EnumExtensions/DimPercentModeExtensions.cs @@ -1,4 +1,3 @@ -#nullable enable using System.CodeDom.Compiler; using System.Diagnostics; diff --git a/Terminal.Gui/ViewBase/EnumExtensions/DimensionExtensions.cs b/Terminal.Gui/ViewBase/EnumExtensions/DimensionExtensions.cs index 0a217e1a8..7d34f36ad 100644 --- a/Terminal.Gui/ViewBase/EnumExtensions/DimensionExtensions.cs +++ b/Terminal.Gui/ViewBase/EnumExtensions/DimensionExtensions.cs @@ -1,4 +1,3 @@ -#nullable enable using System.CodeDom.Compiler; using System.Diagnostics; diff --git a/Terminal.Gui/ViewBase/EnumExtensions/SideExtensions.cs b/Terminal.Gui/ViewBase/EnumExtensions/SideExtensions.cs index f7a2a548f..cbf67f18d 100644 --- a/Terminal.Gui/ViewBase/EnumExtensions/SideExtensions.cs +++ b/Terminal.Gui/ViewBase/EnumExtensions/SideExtensions.cs @@ -1,4 +1,3 @@ -#nullable enable using System.CodeDom.Compiler; using System.Diagnostics; diff --git a/Terminal.Gui/ViewBase/EnumExtensions/ViewDiagnosticFlagsExtensions.cs b/Terminal.Gui/ViewBase/EnumExtensions/ViewDiagnosticFlagsExtensions.cs index e0d089184..400d2d081 100644 --- a/Terminal.Gui/ViewBase/EnumExtensions/ViewDiagnosticFlagsExtensions.cs +++ b/Terminal.Gui/ViewBase/EnumExtensions/ViewDiagnosticFlagsExtensions.cs @@ -1,4 +1,3 @@ -#nullable enable using System.CodeDom.Compiler; using System.Diagnostics; diff --git a/Terminal.Gui/ViewBase/Helpers/StackExtensions.cs b/Terminal.Gui/ViewBase/Helpers/StackExtensions.cs index 96201df73..e76788029 100644 --- a/Terminal.Gui/ViewBase/Helpers/StackExtensions.cs +++ b/Terminal.Gui/ViewBase/Helpers/StackExtensions.cs @@ -1,3 +1,4 @@ +#nullable disable namespace Terminal.Gui.ViewBase; /// Extension of helper to work with specific diff --git a/Terminal.Gui/ViewBase/IDesignable.cs b/Terminal.Gui/ViewBase/IDesignable.cs index 382bdd91b..c7625d079 100644 --- a/Terminal.Gui/ViewBase/IDesignable.cs +++ b/Terminal.Gui/ViewBase/IDesignable.cs @@ -9,10 +9,10 @@ public interface IDesignable /// Causes the View to enable design-time mode. This typically means that the view will load demo data and /// be configured to allow for design-time manipulation. /// - /// Optional arbitrary, View-specific, context. - /// A non-null type for . + /// + /// A non-null type for . /// if the view successfully loaded demo data. - public bool EnableForDesign (ref TContext context) where TContext : notnull => EnableForDesign (); + public bool EnableForDesign (ref TContext targetView) where TContext : notnull => EnableForDesign (); /// /// Causes the View to enable design-time mode. This typically means that the view will load demo data and diff --git a/Terminal.Gui/ViewBase/IMouseHeldDown.cs b/Terminal.Gui/ViewBase/IMouseHeldDown.cs index 5f7435793..d0233fcd3 100644 --- a/Terminal.Gui/ViewBase/IMouseHeldDown.cs +++ b/Terminal.Gui/ViewBase/IMouseHeldDown.cs @@ -1,5 +1,4 @@ -#nullable enable -using System.ComponentModel; +using System.ComponentModel; namespace Terminal.Gui.ViewBase; diff --git a/Terminal.Gui/ViewBase/Layout/Aligner.cs b/Terminal.Gui/ViewBase/Layout/Aligner.cs index 72aae7ad5..c3907bf92 100644 --- a/Terminal.Gui/ViewBase/Layout/Aligner.cs +++ b/Terminal.Gui/ViewBase/Layout/Aligner.cs @@ -1,3 +1,4 @@ +#nullable disable using System.ComponentModel; namespace Terminal.Gui.ViewBase; diff --git a/Terminal.Gui/ViewBase/Layout/Dim.cs b/Terminal.Gui/ViewBase/Layout/Dim.cs index a3326e80a..8952b980a 100644 --- a/Terminal.Gui/ViewBase/Layout/Dim.cs +++ b/Terminal.Gui/ViewBase/Layout/Dim.cs @@ -1,4 +1,3 @@ -#nullable enable namespace Terminal.Gui.ViewBase; using System.Numerics; @@ -188,18 +187,18 @@ public abstract record Dim : IEqualityOperators /// - /// Indicates whether the specified type is in the hierarchy of this Dim object. + /// Indicates whether the specified type is in the hierarchy of this Dim object. /// /// A reference to this instance. /// - public bool Has (out T dim) where T : Dim + public bool Has (out TDim dim) where TDim : Dim { - dim = (this as T)!; + dim = (this as TDim)!; return this switch { - DimCombine combine => combine.Left.Has (out dim) || combine.Right.Has (out dim), - T => true, + DimCombine combine => combine.Left.Has (out dim) || combine.Right.Has (out dim), + TDim => true, _ => false }; } diff --git a/Terminal.Gui/ViewBase/Layout/DimAbsolute.cs b/Terminal.Gui/ViewBase/Layout/DimAbsolute.cs index 6fd9e8072..f630b3470 100644 --- a/Terminal.Gui/ViewBase/Layout/DimAbsolute.cs +++ b/Terminal.Gui/ViewBase/Layout/DimAbsolute.cs @@ -1,4 +1,3 @@ -#nullable enable namespace Terminal.Gui.ViewBase; /// diff --git a/Terminal.Gui/ViewBase/Layout/DimAuto.cs b/Terminal.Gui/ViewBase/Layout/DimAuto.cs index b70aca542..1436eb9bc 100644 --- a/Terminal.Gui/ViewBase/Layout/DimAuto.cs +++ b/Terminal.Gui/ViewBase/Layout/DimAuto.cs @@ -1,4 +1,3 @@ -#nullable enable using System.Diagnostics; namespace Terminal.Gui.ViewBase; @@ -31,8 +30,10 @@ public record DimAuto (Dim? MaximumContentDim, Dim? MinimumContentDim, DimAutoSt var textSize = 0; var maxCalculatedSize = 0; + // 2048 x 2048 supports unit testing where no App is running. + Size screenSize = us.App?.Screen.Size ?? new (2048, 2048); int autoMin = MinimumContentDim?.GetAnchor (superviewContentSize) ?? 0; - int screenX4 = dimension == Dimension.Width ? Application.Screen.Width * 4 : Application.Screen.Height * 4; + int screenX4 = dimension == Dimension.Width ? screenSize.Width * 4 : screenSize.Height * 4; int autoMax = MaximumContentDim?.GetAnchor (superviewContentSize) ?? screenX4; //Debug.WriteLineIf (autoMin > autoMax, "MinimumContentDim must be less than or equal to MaximumContentDim."); diff --git a/Terminal.Gui/ViewBase/Layout/DimAutoStyle.cs b/Terminal.Gui/ViewBase/Layout/DimAutoStyle.cs index d3080dd94..ddd6e0ee5 100644 --- a/Terminal.Gui/ViewBase/Layout/DimAutoStyle.cs +++ b/Terminal.Gui/ViewBase/Layout/DimAutoStyle.cs @@ -1,5 +1,3 @@ - - namespace Terminal.Gui.ViewBase; /// @@ -45,4 +43,4 @@ public enum DimAutoStyle /// corresponding dimension /// Auto = Content | Text, -} \ No newline at end of file +} diff --git a/Terminal.Gui/ViewBase/Layout/DimCombine.cs b/Terminal.Gui/ViewBase/Layout/DimCombine.cs index 3bf3b3534..b246d88bb 100644 --- a/Terminal.Gui/ViewBase/Layout/DimCombine.cs +++ b/Terminal.Gui/ViewBase/Layout/DimCombine.cs @@ -1,4 +1,3 @@ -#nullable enable namespace Terminal.Gui.ViewBase; /// diff --git a/Terminal.Gui/ViewBase/Layout/DimFill.cs b/Terminal.Gui/ViewBase/Layout/DimFill.cs index 0e278cbc5..feda107a0 100644 --- a/Terminal.Gui/ViewBase/Layout/DimFill.cs +++ b/Terminal.Gui/ViewBase/Layout/DimFill.cs @@ -1,4 +1,3 @@ -#nullable enable namespace Terminal.Gui.ViewBase; /// diff --git a/Terminal.Gui/ViewBase/Layout/DimFunc.cs b/Terminal.Gui/ViewBase/Layout/DimFunc.cs index 0773dc316..678406a9b 100644 --- a/Terminal.Gui/ViewBase/Layout/DimFunc.cs +++ b/Terminal.Gui/ViewBase/Layout/DimFunc.cs @@ -1,4 +1,3 @@ -#nullable enable namespace Terminal.Gui.ViewBase; /// diff --git a/Terminal.Gui/ViewBase/Layout/DimPercent.cs b/Terminal.Gui/ViewBase/Layout/DimPercent.cs index 499ffb6fd..2b9ade3b3 100644 --- a/Terminal.Gui/ViewBase/Layout/DimPercent.cs +++ b/Terminal.Gui/ViewBase/Layout/DimPercent.cs @@ -1,4 +1,3 @@ -#nullable enable namespace Terminal.Gui.ViewBase; /// diff --git a/Terminal.Gui/ViewBase/Layout/DimPercentMode.cs b/Terminal.Gui/ViewBase/Layout/DimPercentMode.cs index 3f9aed836..bdb458d38 100644 --- a/Terminal.Gui/ViewBase/Layout/DimPercentMode.cs +++ b/Terminal.Gui/ViewBase/Layout/DimPercentMode.cs @@ -1,5 +1,3 @@ - - namespace Terminal.Gui.ViewBase; /// @@ -16,4 +14,4 @@ public enum DimPercentMode /// The dimension is computed using the View's . /// ContentSize = 1 -} \ No newline at end of file +} diff --git a/Terminal.Gui/ViewBase/Layout/DimView.cs b/Terminal.Gui/ViewBase/Layout/DimView.cs index 0a25e1983..fec551d67 100644 --- a/Terminal.Gui/ViewBase/Layout/DimView.cs +++ b/Terminal.Gui/ViewBase/Layout/DimView.cs @@ -1,4 +1,3 @@ -#nullable enable namespace Terminal.Gui.ViewBase; /// diff --git a/Terminal.Gui/ViewBase/Layout/Dimension.cs b/Terminal.Gui/ViewBase/Layout/Dimension.cs index 5fae94360..60fbcaea6 100644 --- a/Terminal.Gui/ViewBase/Layout/Dimension.cs +++ b/Terminal.Gui/ViewBase/Layout/Dimension.cs @@ -1,5 +1,3 @@ - - namespace Terminal.Gui.ViewBase; /// @@ -21,4 +19,4 @@ public enum Dimension /// The width dimension. /// Width = 2 -} \ No newline at end of file +} diff --git a/Terminal.Gui/ViewBase/Layout/LayoutException.cs b/Terminal.Gui/ViewBase/Layout/LayoutException.cs index e21dc51f8..176f26982 100644 --- a/Terminal.Gui/ViewBase/Layout/LayoutException.cs +++ b/Terminal.Gui/ViewBase/Layout/LayoutException.cs @@ -1,4 +1,3 @@ -#nullable enable namespace Terminal.Gui.ViewBase; /// diff --git a/Terminal.Gui/ViewBase/Layout/Pos.cs b/Terminal.Gui/ViewBase/Layout/Pos.cs index 09694adc3..1e6e00688 100644 --- a/Terminal.Gui/ViewBase/Layout/Pos.cs +++ b/Terminal.Gui/ViewBase/Layout/Pos.cs @@ -1,4 +1,3 @@ -#nullable enable namespace Terminal.Gui.ViewBase; @@ -334,18 +333,18 @@ public abstract record Pos internal virtual bool ReferencesOtherViews () { return false; } /// - /// Indicates whether the specified type is in the hierarchy of this Pos object. + /// Indicates whether the specified type is in the hierarchy of this Pos object. /// /// A reference to this instance. /// - public bool Has (out T pos) where T : Pos + public bool Has (out TPos pos) where TPos : Pos { - pos = (this as T)!; + pos = (this as TPos)!; return this switch { - PosCombine combine => combine.Left.Has (out pos) || combine.Right.Has (out pos), - T => true, + PosCombine combine => combine.Left.Has (out pos) || combine.Right.Has (out pos), + TPos => true, _ => false }; } diff --git a/Terminal.Gui/ViewBase/Layout/PosAbsolute.cs b/Terminal.Gui/ViewBase/Layout/PosAbsolute.cs index b21711355..494c63a50 100644 --- a/Terminal.Gui/ViewBase/Layout/PosAbsolute.cs +++ b/Terminal.Gui/ViewBase/Layout/PosAbsolute.cs @@ -1,4 +1,3 @@ -#nullable enable namespace Terminal.Gui.ViewBase; /// diff --git a/Terminal.Gui/ViewBase/Layout/PosAlign.cs b/Terminal.Gui/ViewBase/Layout/PosAlign.cs index 4d72cc9af..d6289e510 100644 --- a/Terminal.Gui/ViewBase/Layout/PosAlign.cs +++ b/Terminal.Gui/ViewBase/Layout/PosAlign.cs @@ -1,4 +1,3 @@ -#nullable enable using System.ComponentModel; diff --git a/Terminal.Gui/ViewBase/Layout/PosAnchorEnd.cs b/Terminal.Gui/ViewBase/Layout/PosAnchorEnd.cs index 3cf2195ac..d7c6d30a7 100644 --- a/Terminal.Gui/ViewBase/Layout/PosAnchorEnd.cs +++ b/Terminal.Gui/ViewBase/Layout/PosAnchorEnd.cs @@ -1,4 +1,3 @@ -#nullable enable namespace Terminal.Gui.ViewBase; /// diff --git a/Terminal.Gui/ViewBase/Layout/PosCenter.cs b/Terminal.Gui/ViewBase/Layout/PosCenter.cs index 4a7945cd2..d1584f8e5 100644 --- a/Terminal.Gui/ViewBase/Layout/PosCenter.cs +++ b/Terminal.Gui/ViewBase/Layout/PosCenter.cs @@ -1,4 +1,3 @@ -#nullable enable namespace Terminal.Gui.ViewBase; /// diff --git a/Terminal.Gui/ViewBase/Layout/PosCombine.cs b/Terminal.Gui/ViewBase/Layout/PosCombine.cs index 833b499f0..1be5325c2 100644 --- a/Terminal.Gui/ViewBase/Layout/PosCombine.cs +++ b/Terminal.Gui/ViewBase/Layout/PosCombine.cs @@ -1,4 +1,3 @@ -#nullable enable namespace Terminal.Gui.ViewBase; /// diff --git a/Terminal.Gui/ViewBase/Layout/PosFunc.cs b/Terminal.Gui/ViewBase/Layout/PosFunc.cs index 3900beb46..078459fd9 100644 --- a/Terminal.Gui/ViewBase/Layout/PosFunc.cs +++ b/Terminal.Gui/ViewBase/Layout/PosFunc.cs @@ -1,4 +1,3 @@ -#nullable enable namespace Terminal.Gui.ViewBase; /// diff --git a/Terminal.Gui/ViewBase/Layout/PosPercent.cs b/Terminal.Gui/ViewBase/Layout/PosPercent.cs index 98505dd3b..08d968210 100644 --- a/Terminal.Gui/ViewBase/Layout/PosPercent.cs +++ b/Terminal.Gui/ViewBase/Layout/PosPercent.cs @@ -1,4 +1,3 @@ -#nullable enable namespace Terminal.Gui.ViewBase; /// diff --git a/Terminal.Gui/ViewBase/Layout/PosView.cs b/Terminal.Gui/ViewBase/Layout/PosView.cs index fb7f7266a..d42e8cb10 100644 --- a/Terminal.Gui/ViewBase/Layout/PosView.cs +++ b/Terminal.Gui/ViewBase/Layout/PosView.cs @@ -1,4 +1,3 @@ -#nullable enable using System.Diagnostics; namespace Terminal.Gui.ViewBase; diff --git a/Terminal.Gui/ViewBase/Layout/Side.cs b/Terminal.Gui/ViewBase/Layout/Side.cs index b2256faac..0cc52f064 100644 --- a/Terminal.Gui/ViewBase/Layout/Side.cs +++ b/Terminal.Gui/ViewBase/Layout/Side.cs @@ -1,5 +1,3 @@ - - namespace Terminal.Gui.ViewBase; /// @@ -27,4 +25,4 @@ public enum Side /// The bottom (Y + Height) side of the view. /// Bottom = 3 -} \ No newline at end of file +} diff --git a/Terminal.Gui/ViewBase/Layout/SuperViewChangedEventArgs.cs b/Terminal.Gui/ViewBase/Layout/SuperViewChangedEventArgs.cs index f1e79536d..611949a94 100644 --- a/Terminal.Gui/ViewBase/Layout/SuperViewChangedEventArgs.cs +++ b/Terminal.Gui/ViewBase/Layout/SuperViewChangedEventArgs.cs @@ -9,17 +9,17 @@ public class SuperViewChangedEventArgs : EventArgs /// Creates a new instance of the class. /// /// - public SuperViewChangedEventArgs (View superView, View subView) + public SuperViewChangedEventArgs (View? superView, View? subView) { SuperView = superView; SubView = subView; } /// The view that is having it's changed - public View SubView { get; } + public View? SubView { get; } /// /// The parent. For this is the old parent (new parent now being null). /// - public View SuperView { get; } + public View? SuperView { get; } } diff --git a/Terminal.Gui/ViewBase/MouseHeldDown.cs b/Terminal.Gui/ViewBase/MouseHeldDown.cs index ff0764733..f902980a3 100644 --- a/Terminal.Gui/ViewBase/MouseHeldDown.cs +++ b/Terminal.Gui/ViewBase/MouseHeldDown.cs @@ -1,5 +1,4 @@ -#nullable enable -using System.ComponentModel; +using System.ComponentModel; namespace Terminal.Gui.ViewBase; diff --git a/Terminal.Gui/ViewBase/Navigation/FocusEventArgs.cs b/Terminal.Gui/ViewBase/Navigation/FocusEventArgs.cs index 55cc41c6e..d58c68ee3 100644 --- a/Terminal.Gui/ViewBase/Navigation/FocusEventArgs.cs +++ b/Terminal.Gui/ViewBase/Navigation/FocusEventArgs.cs @@ -10,16 +10,15 @@ public class HasFocusEventArgs : CancelEventArgs /// The value will have if the event is not cancelled. /// The view that is losing focus. /// The view that is gaining focus. - public HasFocusEventArgs (bool currentHasFocus, bool newHasFocus, View currentFocused, View newFocused) : base (ref currentHasFocus, ref newHasFocus) + public HasFocusEventArgs (bool currentHasFocus, bool newHasFocus, View? currentFocused, View? newFocused) : base (ref currentHasFocus, ref newHasFocus) { CurrentFocused = currentFocused; NewFocused = newFocused; } /// Gets or sets the view that is losing focus. - public View CurrentFocused { get; set; } + public View? CurrentFocused { get; set; } /// Gets or sets the view that is gaining focus. - public View NewFocused { get; set; } - -} \ No newline at end of file + public View? NewFocused { get; set; } +} diff --git a/Terminal.Gui/ViewBase/Orientation/IOrientation.cs b/Terminal.Gui/ViewBase/Orientation/IOrientation.cs index 4f86d21dd..c56a0cfe5 100644 --- a/Terminal.Gui/ViewBase/Orientation/IOrientation.cs +++ b/Terminal.Gui/ViewBase/Orientation/IOrientation.cs @@ -1,4 +1,4 @@ -#nullable enable + namespace Terminal.Gui.ViewBase; using System; @@ -40,4 +40,4 @@ public interface IOrientation /// /// public void OnOrientationChanged (Orientation newOrientation) { return; } -} \ No newline at end of file +} diff --git a/Terminal.Gui/ViewBase/Orientation/Orientation.cs b/Terminal.Gui/ViewBase/Orientation/Orientation.cs index fa4c766ee..5c847f57a 100644 --- a/Terminal.Gui/ViewBase/Orientation/Orientation.cs +++ b/Terminal.Gui/ViewBase/Orientation/Orientation.cs @@ -1,4 +1,4 @@ -#nullable enable + namespace Terminal.Gui.ViewBase; /// Direction of an element (horizontal or vertical) diff --git a/Terminal.Gui/ViewBase/Orientation/OrientationHelper.cs b/Terminal.Gui/ViewBase/Orientation/OrientationHelper.cs index a7079128c..dfd8a3d44 100644 --- a/Terminal.Gui/ViewBase/Orientation/OrientationHelper.cs +++ b/Terminal.Gui/ViewBase/Orientation/OrientationHelper.cs @@ -1,4 +1,4 @@ -#nullable enable + namespace Terminal.Gui.ViewBase; diff --git a/Terminal.Gui/ViewBase/View.Adornments.cs b/Terminal.Gui/ViewBase/View.Adornments.cs index de0ca20a1..97d2da40c 100644 --- a/Terminal.Gui/ViewBase/View.Adornments.cs +++ b/Terminal.Gui/ViewBase/View.Adornments.cs @@ -1,5 +1,4 @@ -#nullable enable - + namespace Terminal.Gui.ViewBase; public partial class View // Adornments diff --git a/Terminal.Gui/ViewBase/View.Arrangement.cs b/Terminal.Gui/ViewBase/View.Arrangement.cs index dba4f6c83..58323f11a 100644 --- a/Terminal.Gui/ViewBase/View.Arrangement.cs +++ b/Terminal.Gui/ViewBase/View.Arrangement.cs @@ -1,5 +1,4 @@ -#nullable enable -namespace Terminal.Gui.ViewBase; +namespace Terminal.Gui.ViewBase; public partial class View { diff --git a/Terminal.Gui/ViewBase/View.Command.cs b/Terminal.Gui/ViewBase/View.Command.cs index 0c3a741ac..ca8de67a1 100644 --- a/Terminal.Gui/ViewBase/View.Command.cs +++ b/Terminal.Gui/ViewBase/View.Command.cs @@ -1,5 +1,4 @@ -#nullable enable - + namespace Terminal.Gui.ViewBase; public partial class View // Command APIs diff --git a/Terminal.Gui/ViewBase/View.Content.cs b/Terminal.Gui/ViewBase/View.Content.cs index cbb29308a..8d6345a65 100644 --- a/Terminal.Gui/ViewBase/View.Content.cs +++ b/Terminal.Gui/ViewBase/View.Content.cs @@ -1,5 +1,4 @@ -#nullable enable - + namespace Terminal.Gui.ViewBase; public partial class View diff --git a/Terminal.Gui/ViewBase/View.Cursor.cs b/Terminal.Gui/ViewBase/View.Cursor.cs index daaf75d69..d710a273e 100644 --- a/Terminal.Gui/ViewBase/View.Cursor.cs +++ b/Terminal.Gui/ViewBase/View.Cursor.cs @@ -1,4 +1,3 @@ -#nullable enable namespace Terminal.Gui.ViewBase; diff --git a/Terminal.Gui/ViewBase/View.Diagnostics.cs b/Terminal.Gui/ViewBase/View.Diagnostics.cs index d920ef4bf..19f77eac7 100644 --- a/Terminal.Gui/ViewBase/View.Diagnostics.cs +++ b/Terminal.Gui/ViewBase/View.Diagnostics.cs @@ -1,5 +1,4 @@ -#nullable enable -namespace Terminal.Gui.ViewBase; +namespace Terminal.Gui.ViewBase; public partial class View { diff --git a/Terminal.Gui/ViewBase/View.Drawing.Attribute.cs b/Terminal.Gui/ViewBase/View.Drawing.Attribute.cs index b43a426d2..81167c439 100644 --- a/Terminal.Gui/ViewBase/View.Drawing.Attribute.cs +++ b/Terminal.Gui/ViewBase/View.Drawing.Attribute.cs @@ -1,5 +1,4 @@ -#nullable enable -using System.ComponentModel; +using System.ComponentModel; namespace Terminal.Gui.ViewBase; diff --git a/Terminal.Gui/ViewBase/View.Drawing.Clipping.cs b/Terminal.Gui/ViewBase/View.Drawing.Clipping.cs index 49a0e1fe3..6a5bbeb67 100644 --- a/Terminal.Gui/ViewBase/View.Drawing.Clipping.cs +++ b/Terminal.Gui/ViewBase/View.Drawing.Clipping.cs @@ -1,5 +1,4 @@ -#nullable enable - + namespace Terminal.Gui.ViewBase; public partial class View @@ -16,7 +15,7 @@ public partial class View /// /// /// The current Clip. - public static Region? GetClip () { return Application.Driver?.Clip; } + public Region? GetClip () => Driver?.Clip; /// /// Sets the Clip to the specified region. @@ -28,11 +27,13 @@ public partial class View /// /// /// - public static void SetClip (Region? region) + public void SetClip (Region? region) { - if (Application.Driver is { } && region is { }) + // BUGBUG: If region is null we should set the clip to null. + // BUGBUG: Fixing this probably breaks other things. + if (Driver is { } && region is { }) { - Application.Driver.Clip = region; + Driver.Clip = region; } } @@ -51,13 +52,13 @@ public partial class View /// /// The current Clip, which can be then re-applied /// - public static Region? SetClipToScreen () + public Region? SetClipToScreen () { Region? previous = GetClip (); - if (Application.Driver is { }) + if (Driver is { }) { - Application.Driver.Clip = new (Application.Screen); + Driver.Clip = new (Driver!.Screen); } return previous; @@ -72,7 +73,7 @@ public partial class View /// /// /// - public static void ExcludeFromClip (Rectangle rectangle) { Application.Driver?.Clip?.Exclude (rectangle); } + public void ExcludeFromClip (Rectangle rectangle) { Driver?.Clip?.Exclude (rectangle); } /// /// Removes the specified rectangle from the Clip. @@ -83,7 +84,7 @@ public partial class View /// /// /// - public static void ExcludeFromClip (Region? region) { Application.Driver?.Clip?.Exclude (region); } + public void ExcludeFromClip (Region? region) { Driver?.Clip?.Exclude (region); } /// /// Changes the Clip to the intersection of the current Clip and the of this View. @@ -104,7 +105,7 @@ public partial class View return null; } - Region previous = GetClip () ?? new (Application.Screen); + Region previous = GetClip () ?? new (Driver.Screen); Region frameRegion = previous.Clone (); @@ -151,7 +152,7 @@ public partial class View return null; } - Region previous = GetClip () ?? new (Application.Screen); + Region previous = GetClip () ?? new (App!.Screen); Region viewportRegion = previous.Clone (); diff --git a/Terminal.Gui/ViewBase/View.Drawing.Primitives.cs b/Terminal.Gui/ViewBase/View.Drawing.Primitives.cs index d9d9333ee..6b169718b 100644 --- a/Terminal.Gui/ViewBase/View.Drawing.Primitives.cs +++ b/Terminal.Gui/ViewBase/View.Drawing.Primitives.cs @@ -63,7 +63,7 @@ public partial class View /// /// /// When the method returns, the draw position will be incremented by the number of columns - /// required, unless the new column value is outside the or . + /// required, unless the new column value is outside the or . /// /// If requires more columns than are available, the output will be clipped. /// @@ -121,8 +121,8 @@ public partial class View { DrawHotString ( text, - Enabled ? GetAttributeForRole (VisualRole.HotNormal) : GetScheme ()!.Disabled, - Enabled ? GetAttributeForRole (VisualRole.Normal) : GetScheme ()!.Disabled + Enabled ? GetAttributeForRole (VisualRole.HotNormal) : GetScheme ().Disabled, + Enabled ? GetAttributeForRole (VisualRole.Normal) : GetScheme ().Disabled ); } } @@ -137,7 +137,7 @@ public partial class View return; } - Region prevClip = AddViewportToClip (); + Region? prevClip = AddViewportToClip (); Rectangle toClear = ViewportToScreen (rect); Attribute prev = SetAttribute (new (color ?? GetAttributeForRole (VisualRole.Normal).Background)); Driver.FillRect (toClear); @@ -155,7 +155,7 @@ public partial class View return; } - Region prevClip = AddViewportToClip (); + Region? prevClip = AddViewportToClip (); Rectangle toClear = ViewportToScreen (rect); Driver.FillRect (toClear, rune); SetClip (prevClip); diff --git a/Terminal.Gui/ViewBase/View.Drawing.Scheme.cs b/Terminal.Gui/ViewBase/View.Drawing.Scheme.cs index dcd28794b..5ad8c0101 100644 --- a/Terminal.Gui/ViewBase/View.Drawing.Scheme.cs +++ b/Terminal.Gui/ViewBase/View.Drawing.Scheme.cs @@ -1,5 +1,4 @@ -#nullable enable -namespace Terminal.Gui.ViewBase; +namespace Terminal.Gui.ViewBase; public partial class View { diff --git a/Terminal.Gui/ViewBase/View.Drawing.cs b/Terminal.Gui/ViewBase/View.Drawing.cs index 1f2756123..ed300ee5d 100644 --- a/Terminal.Gui/ViewBase/View.Drawing.cs +++ b/Terminal.Gui/ViewBase/View.Drawing.cs @@ -1,5 +1,4 @@ -#nullable enable -using System.ComponentModel; +using System.ComponentModel; using System.Diagnostics; namespace Terminal.Gui.ViewBase; @@ -206,7 +205,7 @@ public partial class View // Drawing APIs if (Margin?.NeedsLayout == true) { Margin.NeedsLayout = false; - Margin?.Thickness.Draw (FrameToScreen ()); + Margin?.Thickness.Draw (Driver, FrameToScreen ()); Margin?.Parent?.SetSubViewNeedsDraw (); } @@ -446,12 +445,15 @@ public partial class View // Drawing APIs // Report the drawn area to the context context?.AddDrawnRegion (textRegion); - TextFormatter?.Draw ( - drawRect, - HasFocus ? GetAttributeForRole (VisualRole.Focus) : GetAttributeForRole (VisualRole.Normal), - HasFocus ? GetAttributeForRole (VisualRole.HotFocus) : GetAttributeForRole (VisualRole.HotNormal), - Rectangle.Empty - ); + if (Driver is { }) + { + TextFormatter?.Draw ( + Driver, + drawRect, + HasFocus ? GetAttributeForRole (VisualRole.Focus) : GetAttributeForRole (VisualRole.Normal), + HasFocus ? GetAttributeForRole (VisualRole.HotFocus) : GetAttributeForRole (VisualRole.HotNormal), + Rectangle.Empty); + } // We assume that the text has been drawn over the entire area; ensure that the subviews are redrawn. SetSubViewNeedsDraw (); diff --git a/Terminal.Gui/ViewBase/View.Hierarchy.cs b/Terminal.Gui/ViewBase/View.Hierarchy.cs index a80193c38..b88a07199 100644 --- a/Terminal.Gui/ViewBase/View.Hierarchy.cs +++ b/Terminal.Gui/ViewBase/View.Hierarchy.cs @@ -1,4 +1,3 @@ -#nullable enable using System.Collections.ObjectModel; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; @@ -363,12 +362,12 @@ public partial class View // SuperView/SubView hierarchy management (SuperView, #endregion AddRemove - // TODO: This drives a weird coupling of Application.Top and View. It's not clear why this is needed. + // TODO: This drives a weird coupling of Application.Current and View. It's not clear why this is needed. /// Get the top superview of a given . /// The superview view. internal View? GetTopSuperView (View? view = null, View? superview = null) { - View? top = superview ?? Application.Top; + View? top = superview ?? App?.Current; for (View? v = view?.SuperView ?? this?.SuperView; v != null; v = v.SuperView) { diff --git a/Terminal.Gui/ViewBase/View.Keyboard.cs b/Terminal.Gui/ViewBase/View.Keyboard.cs index 51350dca7..8f9a5127a 100644 --- a/Terminal.Gui/ViewBase/View.Keyboard.cs +++ b/Terminal.Gui/ViewBase/View.Keyboard.cs @@ -1,5 +1,4 @@ -#nullable enable - + namespace Terminal.Gui.ViewBase; public partial class View // Keyboard APIs diff --git a/Terminal.Gui/ViewBase/View.Layout.cs b/Terminal.Gui/ViewBase/View.Layout.cs index 0c73aacbb..ecd58e737 100644 --- a/Terminal.Gui/ViewBase/View.Layout.cs +++ b/Terminal.Gui/ViewBase/View.Layout.cs @@ -1,4 +1,3 @@ -#nullable enable using System.Diagnostics; namespace Terminal.Gui.ViewBase; @@ -438,10 +437,10 @@ public partial class View // Layout APIs private void NeedsClearScreenNextIteration () { - if (Application.Top is { } && Application.Top == this && Application.TopLevels.Count == 1) + if (App is { Current: { } } && App.Current == this && App.SessionStack.Count == 1) { // If this is the only TopLevel, we need to redraw the screen - Application.ClearScreenNextIteration = true; + App.ClearScreenNextIteration = true; } } @@ -532,7 +531,7 @@ public partial class View // Layout APIs /// /// Performs layout of the view and its subviews using the content size of either the or - /// . + /// . /// /// /// @@ -545,7 +544,7 @@ public partial class View // Layout APIs /// /// /// If the view could not be laid out (typically because dependency was not ready). - public bool Layout () { return Layout (GetContainerSize ()); } + public bool Layout () => Layout (GetContainerSize ()); /// /// Sets the position and size of this view, relative to the SuperView's ContentSize (nominally the same as @@ -1114,11 +1113,12 @@ public partial class View // Layout APIs { // TODO: Get rid of refs to Top Size superViewContentSize = SuperView?.GetContentSize () - ?? (Application.Top is { } && Application.Top != this && Application.Top.IsInitialized - ? Application.Top.GetContentSize () - : Application.Screen.Size); + ?? (App?.Current is { } && App?.Current != this && App!.Current.IsInitialized + ? App.Current.GetContentSize () + : App?.Screen.Size ?? new (2048, 2048)); return superViewContentSize; + } // BUGBUG: This method interferes with Dialog/MessageBox default min/max size. @@ -1130,7 +1130,7 @@ public partial class View // Layout APIs /// /// /// If does not have a or it's SuperView is not - /// the position will be bound by . + /// the position will be bound by . /// /// The View that is to be moved. /// The target x location. @@ -1138,7 +1138,7 @@ public partial class View // Layout APIs /// The new x location that will ensure will be fully visible. /// The new y location that will ensure will be fully visible. /// - /// Either (if does not have a Super View) or + /// Either (if does not have a Super View) or /// 's SuperView. This can be used to ensure LayoutSubViews is called on the correct View. /// internal static View? GetLocationEnsuringFullVisibility ( @@ -1152,10 +1152,12 @@ public partial class View // Layout APIs int maxDimension; View? superView; - if (viewToMove?.SuperView is null || viewToMove == Application.Top || viewToMove?.SuperView == Application.Top) + IApplication? app = viewToMove.App; + + if (viewToMove?.SuperView is null || viewToMove == app?.Current || viewToMove?.SuperView == app?.Current) { - maxDimension = Application.Screen.Width; - superView = Application.Top; + maxDimension = app?.Screen.Width ?? 0; + superView = app?.Current; } else { @@ -1188,9 +1190,9 @@ public partial class View // Layout APIs var menuVisible = false; var statusVisible = false; - if (viewToMove?.SuperView is null || viewToMove == Application.Top || viewToMove?.SuperView == Application.Top) + if (viewToMove?.SuperView is null || viewToMove == app?.Current || viewToMove?.SuperView == app?.Current) { - menuVisible = Application.Top?.MenuBar?.Visible == true; + menuVisible = app?.Current?.MenuBar?.Visible == true; } else { @@ -1207,7 +1209,7 @@ public partial class View // Layout APIs } } - if (viewToMove?.SuperView is null || viewToMove == Application.Top || viewToMove?.SuperView == Application.Top) + if (viewToMove?.SuperView is null || viewToMove == app?.Current || viewToMove?.SuperView == app?.Current) { maxDimension = menuVisible ? 1 : 0; } @@ -1218,9 +1220,16 @@ public partial class View // Layout APIs ny = Math.Max (targetY, maxDimension); - if (viewToMove?.SuperView is null || viewToMove == Application.Top || viewToMove?.SuperView == Application.Top) + if (viewToMove?.SuperView is null || viewToMove == app?.Current || viewToMove?.SuperView == app?.Current) { - maxDimension = statusVisible ? Application.Screen.Height - 1 : Application.Screen.Height; + if (app is { }) + { + maxDimension = statusVisible ? app.Screen.Height - 1 : app.Screen.Height; + } + else + { + maxDimension = 0; + } } else { @@ -1267,10 +1276,10 @@ public partial class View // Layout APIs /// /// flags set in their ViewportSettings. /// - public static List GetViewsUnderLocation (in Point screenLocation, ViewportSettingsFlags excludeViewportSettingsFlags) + public List GetViewsUnderLocation (in Point screenLocation, ViewportSettingsFlags excludeViewportSettingsFlags) { // PopoverHost - If visible, start with it instead of Top - if (Application.Popover?.GetActivePopover () is View { Visible: true } visiblePopover) + if (App?.Popover?.GetActivePopover () is View { Visible: true } visiblePopover) { // BUGBUG: We do not traverse all visible toplevels if there's an active popover. This may be a bug. List result = []; @@ -1286,9 +1295,9 @@ public partial class View // Layout APIs var checkedTop = false; // Traverse all visible toplevels, topmost first (reverse stack order) - if (Application.TopLevels.Count > 0) + if (App?.SessionStack.Count > 0) { - foreach (Toplevel toplevel in Application.TopLevels) + foreach (Toplevel toplevel in App.SessionStack) { if (toplevel.Visible && toplevel.Contains (screenLocation)) { @@ -1301,7 +1310,7 @@ public partial class View // Layout APIs } } - if (toplevel == Application.Top) + if (toplevel == App.Current) { checkedTop = true; } @@ -1309,7 +1318,7 @@ public partial class View // Layout APIs } // Fallback: If TopLevels is empty or Top is not in TopLevels, check Top directly (for test compatibility) - if (!checkedTop && Application.Top is { Visible: true } top) + if (!checkedTop && App?.Current is { Visible: true } top) { // For root toplevels, allow hit-testing even if location is outside bounds (for drag/move) List result = GetViewsUnderLocation (top, screenLocation, excludeViewportSettingsFlags); diff --git a/Terminal.Gui/ViewBase/View.Mouse.cs b/Terminal.Gui/ViewBase/View.Mouse.cs index 546017059..c899c2d2e 100644 --- a/Terminal.Gui/ViewBase/View.Mouse.cs +++ b/Terminal.Gui/ViewBase/View.Mouse.cs @@ -1,5 +1,4 @@ -#nullable enable -using System.ComponentModel; +using System.ComponentModel; namespace Terminal.Gui.ViewBase; @@ -16,7 +15,7 @@ public partial class View // Mouse APIs private void SetupMouse () { - MouseHeldDown = new MouseHeldDown (this, Application.TimedEvents,Application.Mouse); + MouseHeldDown = new MouseHeldDown (this, App?.TimedEvents, App?.Mouse); MouseBindings = new (); // TODO: Should the default really work with any button or just button1? @@ -54,7 +53,7 @@ public partial class View // Mouse APIs #region MouseEnterLeave /// - /// INTERNAL Called by when the mouse moves over the View's + /// INTERNAL Called by when the mouse moves over the View's /// . /// will /// be raised when the mouse is no longer over the . If another View occludes this View, the @@ -151,7 +150,7 @@ public partial class View // Mouse APIs public event EventHandler? MouseEnter; /// - /// INTERNAL Called by when the mouse leaves , or is + /// INTERNAL Called by when the mouse leaves , or is /// occluded /// by another non-SubView. /// @@ -228,7 +227,7 @@ public partial class View // Mouse APIs public bool WantMousePositionReports { get; set; } /// - /// Processes a new . This method is called by when a + /// Processes a new . This method is called by when a /// mouse /// event occurs. /// @@ -375,7 +374,7 @@ public partial class View // Mouse APIs if (mouseEvent.IsReleased) { - if (Application.Mouse.MouseGrabView == this) + if (App?.Mouse.MouseGrabView == this) { //Logging.Debug ($"{Id} - {MouseState}"); MouseState &= ~MouseState.Pressed; @@ -407,9 +406,9 @@ public partial class View // Mouse APIs if (mouseEvent.IsPressed) { // The first time we get pressed event, grab the mouse and set focus - if (Application.Mouse.MouseGrabView != this) + if (App?.Mouse.MouseGrabView != this) { - Application.Mouse.GrabMouse (this); + App?.Mouse.GrabMouse (this); if (!HasFocus && CanFocus) { @@ -541,10 +540,10 @@ public partial class View // Mouse APIs { mouseEvent.Handled = false; - if (Application.Mouse.MouseGrabView == this && mouseEvent.IsSingleClicked) + if (App?.Mouse.MouseGrabView == this && mouseEvent.IsSingleClicked) { // We're grabbed. Clicked event comes after the last Release. This is our signal to ungrab - Application.Mouse.UngrabMouse (); + App?.Mouse.UngrabMouse (); // TODO: Prove we need to unset MouseState.Pressed and MouseState.PressedOutside here // TODO: There may be perf gains if we don't unset these flags here @@ -695,4 +694,4 @@ public partial class View // Mouse APIs #endregion MouseState Handling private void DisposeMouse () { } -} \ No newline at end of file +} diff --git a/Terminal.Gui/ViewBase/View.Navigation.cs b/Terminal.Gui/ViewBase/View.Navigation.cs index 516aef3c0..5ff60aa7e 100644 --- a/Terminal.Gui/ViewBase/View.Navigation.cs +++ b/Terminal.Gui/ViewBase/View.Navigation.cs @@ -1,4 +1,3 @@ -#nullable enable using System.Diagnostics; @@ -396,7 +395,7 @@ public partial class View // Focus and cross-view navigation management (TabStop public event EventHandler? FocusedChanged; /// Returns a value indicating if this View is currently on Top (Active) - public bool IsCurrentTop => Application.Top == this; + public bool IsCurrentTop => App?.Current == this; /// /// Returns the most focused SubView down the subview-hierarchy. @@ -520,7 +519,7 @@ public partial class View // Focus and cross-view navigation management (TabStop if (value) { // NOTE: If Application.Navigation is null, we pass null to FocusChanging. For unit tests. - (bool focusSet, bool _) = SetHasFocusTrue (Application.Navigation?.GetFocused ()); + (bool focusSet, bool _) = SetHasFocusTrue (App?.Navigation?.GetFocused ()); if (focusSet) { @@ -557,7 +556,7 @@ public partial class View // Focus and cross-view navigation management (TabStop /// if the focus changed; false otherwise. public bool SetFocus () { - (bool focusSet, bool _) = SetHasFocusTrue (Application.Navigation?.GetFocused ()); + (bool focusSet, bool _) = SetHasFocusTrue (App?.Navigation?.GetFocused ()); return focusSet; } @@ -722,17 +721,17 @@ public partial class View // Focus and cross-view navigation management (TabStop return true; } - View? appFocused = Application.Navigation?.GetFocused (); + View? appFocused = App?.Navigation?.GetFocused (); if (appFocused == currentFocused) { if (newFocused is { HasFocus: true }) { - Application.Navigation?.SetFocused (newFocused); + App?.Navigation?.SetFocused (newFocused); } else { - Application.Navigation?.SetFocused (null); + App?.Navigation?.SetFocused (null); } } @@ -835,7 +834,7 @@ public partial class View // Focus and cross-view navigation management (TabStop } // Application.Navigation.GetFocused? - View? applicationFocused = Application.Navigation?.GetFocused (); + View? applicationFocused = App?.Navigation?.GetFocused (); if (newFocusedView is null && applicationFocused != this && applicationFocused is { CanFocus: true }) { @@ -854,18 +853,18 @@ public partial class View // Focus and cross-view navigation management (TabStop } } - // Application.Top? - if (newFocusedView is null && Application.Top is { CanFocus: true, HasFocus: false }) + // Application.Current? + if (newFocusedView is null && App?.Current is { CanFocus: true, HasFocus: false }) { // Temporarily ensure this view can't get focus bool prevCanFocus = _canFocus; _canFocus = false; - bool restoredFocus = Application.Top.RestoreFocus (); + bool restoredFocus = App?.Current.RestoreFocus () ?? false; _canFocus = prevCanFocus; - if (Application.Top is { CanFocus: true, HasFocus: true }) + if (App?.Current is { CanFocus: true, HasFocus: true }) { - newFocusedView = Application.Top; + newFocusedView = App?.Current; } else if (restoredFocus) { @@ -952,7 +951,7 @@ public partial class View // Focus and cross-view navigation management (TabStop // If we are the most focused view, we need to set the focused view in Application.Navigation if (newHasFocus && focusedView?.Focused is null) { - Application.Navigation?.SetFocused (focusedView); + App?.Navigation?.SetFocused (focusedView); } // Call the virtual method diff --git a/Terminal.Gui/ViewBase/View.ScrollBars.cs b/Terminal.Gui/ViewBase/View.ScrollBars.cs index 4463299f9..fd5f6a327 100644 --- a/Terminal.Gui/ViewBase/View.ScrollBars.cs +++ b/Terminal.Gui/ViewBase/View.ScrollBars.cs @@ -1,5 +1,4 @@ -#nullable enable - + namespace Terminal.Gui.ViewBase; public partial class View diff --git a/Terminal.Gui/ViewBase/View.Text.cs b/Terminal.Gui/ViewBase/View.Text.cs index 32694d33f..b5b401f81 100644 --- a/Terminal.Gui/ViewBase/View.Text.cs +++ b/Terminal.Gui/ViewBase/View.Text.cs @@ -1,4 +1,3 @@ -#nullable enable namespace Terminal.Gui.ViewBase; diff --git a/Terminal.Gui/ViewBase/View.cs b/Terminal.Gui/ViewBase/View.cs index 5aa8a6e1d..68325dfe9 100644 --- a/Terminal.Gui/ViewBase/View.cs +++ b/Terminal.Gui/ViewBase/View.cs @@ -1,5 +1,4 @@ -#nullable enable -using System.Collections.Concurrent; +using System.Collections.Concurrent; using System.ComponentModel; using System.Diagnostics; @@ -52,7 +51,7 @@ public partial class View : IDisposable, ISupportInitializeNotification /// Pretty prints the View /// - public override string ToString () { return $"{GetType ().Name}({Id}){Frame}"; } + public override string ToString () => $"{GetType ().Name}({Id}){Frame}"; /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. /// @@ -72,9 +71,9 @@ public partial class View : IDisposable, ISupportInitializeNotification DisposeAdornments (); DisposeScrollBars (); - if (Application.Mouse.MouseGrabView == this) + if (App?.Mouse.MouseGrabView == this) { - Application.Mouse.UngrabMouse (); + App.Mouse.UngrabMouse (); } for (int i = InternalSubViews.Count - 1; i >= 0; i--) @@ -109,6 +108,34 @@ public partial class View : IDisposable, ISupportInitializeNotification /// The id should be unique across all Views that share a SuperView. public string Id { get; set; } = ""; + private IApplication? _app; + + /// + /// Gets the instance this view is running in. If this view is at the top of the view + /// hierarchy, returns . + /// + /// + /// + /// If not explicitly set on an instance, this property will retrieve the value from the view at the top + /// of the View hierarchy (the top-most SuperView). + /// + /// + public IApplication? App + { + get => GetApp (); + internal set => _app = value; + } + + /// + /// Gets the instance this view is running in. Used internally to allow overrides by + /// . + /// + /// + /// If this view is at the top of the view hierarchy, and was not explicitly set, + /// returns . + /// + protected virtual IApplication? GetApp () => _app ?? SuperView?.App ?? null; + private IDriver? _driver; /// @@ -118,19 +145,21 @@ public partial class View : IDisposable, ISupportInitializeNotification /// internal IDriver? Driver { - get - { - if (_driver is { }) - { - return _driver; - } - - return Application.Driver; - } + get => GetDriver (); set => _driver = value; } - /// Gets the screen buffer contents. This is a convenience property for Views that need direct access to the screen buffer. + /// + /// Gets the instance for this view. Used internally to allow overrides by + /// . + /// + /// If this view is at the top of the view hierarchy, returns . + protected virtual IDriver? GetDriver () => _driver ?? App?.Driver ?? SuperView?.Driver /*?? ApplicationImpl.Instance.Driver*/; + + /// + /// Gets the screen buffer contents. This is a convenience property for Views that need direct access to the + /// screen buffer. + /// protected Cell [,]? ScreenContents => Driver?.Contents; /// Initializes a new instance of . @@ -387,7 +416,7 @@ public partial class View : IDisposable, ISupportInitializeNotification } /// Called when is changing. Can be cancelled by returning . - protected virtual bool OnVisibleChanging () { return false; } + protected virtual bool OnVisibleChanging () => false; /// /// Raised when the value is being changed. Can be cancelled by setting Cancel to diff --git a/Terminal.Gui/ViewBase/ViewDiagnosticFlags.cs b/Terminal.Gui/ViewBase/ViewDiagnosticFlags.cs index 345ec3c78..94959a874 100644 --- a/Terminal.Gui/ViewBase/ViewDiagnosticFlags.cs +++ b/Terminal.Gui/ViewBase/ViewDiagnosticFlags.cs @@ -1,5 +1,4 @@ -#nullable enable -namespace Terminal.Gui.ViewBase; +namespace Terminal.Gui.ViewBase; /// Enables diagnostic functions for . [Flags] diff --git a/Terminal.Gui/ViewBase/ViewEventArgs.cs b/Terminal.Gui/ViewBase/ViewEventArgs.cs index d2de59ec0..e5cc5e36c 100644 --- a/Terminal.Gui/ViewBase/ViewEventArgs.cs +++ b/Terminal.Gui/ViewBase/ViewEventArgs.cs @@ -13,4 +13,4 @@ public class ViewEventArgs : EventArgs /// child then sender may be the parent while is the child being added. /// public View View { get; } -} \ No newline at end of file +} diff --git a/Terminal.Gui/Views/Autocomplete/AppendAutocomplete.cs b/Terminal.Gui/Views/Autocomplete/AppendAutocomplete.cs index b53971624..f8452862e 100644 --- a/Terminal.Gui/Views/Autocomplete/AppendAutocomplete.cs +++ b/Terminal.Gui/Views/Autocomplete/AppendAutocomplete.cs @@ -1,3 +1,4 @@ +#nullable disable  namespace Terminal.Gui.Views; @@ -8,12 +9,12 @@ namespace Terminal.Gui.Views; public class AppendAutocomplete : AutocompleteBase { private bool _suspendSuggestions; - private TextField textField; + private TextField _textField; /// Creates a new instance of the class. public AppendAutocomplete (TextField textField) { - this.textField = textField; + _textField = textField; base.SelectionKey = KeyCode.Tab; Scheme = new Scheme @@ -35,15 +36,15 @@ public class AppendAutocomplete : AutocompleteBase /// public override View HostControl { - get => textField; - set => textField = (TextField)value; + get => _textField; + set => _textField = (TextField)value; } /// public override void ClearSuggestions () { base.ClearSuggestions (); - textField.SetNeedsDraw (); + _textField.SetNeedsDraw (); } /// @@ -107,19 +108,19 @@ public class AppendAutocomplete : AutocompleteBase } // draw it like it's selected, even though it's not - textField.SetAttribute ( + _textField.SetAttribute ( new Attribute ( Scheme.Normal.Foreground, - textField.GetAttributeForRole(VisualRole.Focus).Background, + _textField.GetAttributeForRole(VisualRole.Focus).Background, Scheme.Normal.Style ) ); - textField.Move (textField.Text.Length, 0); + _textField.Move (_textField.Text.Length, 0); Suggestion suggestion = Suggestions.ElementAt (SelectedIdx); string fragment = suggestion.Replacement.Substring (suggestion.Remove); - int spaceAvailable = textField.Viewport.Width - textField.Text.GetColumns (); + int spaceAvailable = _textField.Viewport.Width - _textField.Text.GetColumns (); int spaceRequired = fragment.EnumerateRunes ().Sum (c => c.GetColumns ()); if (spaceAvailable < spaceRequired) @@ -130,7 +131,7 @@ public class AppendAutocomplete : AutocompleteBase ); } - Application.Driver?.AddStr (fragment); + _textField.Driver?.AddStr (fragment); } /// @@ -143,12 +144,12 @@ public class AppendAutocomplete : AutocompleteBase if (MakingSuggestion ()) { Suggestion insert = Suggestions.ElementAt (SelectedIdx); - string newText = textField.Text; + string newText = _textField.Text; newText = newText.Substring (0, newText.Length - insert.Remove); newText += insert.Replacement; - textField.Text = newText; + _textField.Text = newText; - textField.MoveEnd (); + _textField.MoveEnd (); ClearSuggestions (); @@ -167,8 +168,8 @@ public class AppendAutocomplete : AutocompleteBase newText += Path.DirectorySeparatorChar; } - textField.Text = newText; - textField.MoveEnd (); + _textField.Text = newText; + _textField.MoveEnd (); } private bool CycleSuggestion (int direction) @@ -185,7 +186,7 @@ public class AppendAutocomplete : AutocompleteBase SelectedIdx = Suggestions.Count () - 1; } - textField.SetNeedsDraw (); + _textField.SetNeedsDraw (); return true; } @@ -195,5 +196,5 @@ public class AppendAutocomplete : AutocompleteBase /// to see auto-complete (i.e. focused and cursor in right place). /// /// - private bool MakingSuggestion () { return Suggestions.Any () && SelectedIdx != -1 && textField.HasFocus && textField.CursorIsAtEnd (); } + private bool MakingSuggestion () { return Suggestions.Any () && SelectedIdx != -1 && _textField.HasFocus && _textField.CursorIsAtEnd (); } } diff --git a/Terminal.Gui/Views/Autocomplete/AutocompleteBase.cs b/Terminal.Gui/Views/Autocomplete/AutocompleteBase.cs index f2122fa4b..d8abc050f 100644 --- a/Terminal.Gui/Views/Autocomplete/AutocompleteBase.cs +++ b/Terminal.Gui/Views/Autocomplete/AutocompleteBase.cs @@ -1,3 +1,4 @@ +#nullable disable using System.Collections.ObjectModel; namespace Terminal.Gui.Views; diff --git a/Terminal.Gui/Views/Autocomplete/AutocompleteContext.cs b/Terminal.Gui/Views/Autocomplete/AutocompleteContext.cs index d2e7e4d59..bd9f32712 100644 --- a/Terminal.Gui/Views/Autocomplete/AutocompleteContext.cs +++ b/Terminal.Gui/Views/Autocomplete/AutocompleteContext.cs @@ -1,4 +1,5 @@ +#nullable disable namespace Terminal.Gui.Views; /// diff --git a/Terminal.Gui/Views/Autocomplete/AutocompleteFilepathContext.cs b/Terminal.Gui/Views/Autocomplete/AutocompleteFilepathContext.cs index 6c3e12c34..80481a787 100644 --- a/Terminal.Gui/Views/Autocomplete/AutocompleteFilepathContext.cs +++ b/Terminal.Gui/Views/Autocomplete/AutocompleteFilepathContext.cs @@ -1,3 +1,4 @@ +#nullable disable using System.IO.Abstractions; using System.Runtime.InteropServices; diff --git a/Terminal.Gui/Views/Autocomplete/IAutocomplete.cs b/Terminal.Gui/Views/Autocomplete/IAutocomplete.cs index 717122e9d..ce15a641e 100644 --- a/Terminal.Gui/Views/Autocomplete/IAutocomplete.cs +++ b/Terminal.Gui/Views/Autocomplete/IAutocomplete.cs @@ -1,3 +1,4 @@ +#nullable disable using System.Collections.ObjectModel; namespace Terminal.Gui.Views; diff --git a/Terminal.Gui/Views/Autocomplete/ISuggestionGenerator.cs b/Terminal.Gui/Views/Autocomplete/ISuggestionGenerator.cs index 08fd17c9a..e8eedd2ee 100644 --- a/Terminal.Gui/Views/Autocomplete/ISuggestionGenerator.cs +++ b/Terminal.Gui/Views/Autocomplete/ISuggestionGenerator.cs @@ -1,3 +1,4 @@ +#nullable disable namespace Terminal.Gui.Views; /// Generates autocomplete based on a given cursor location within a string diff --git a/Terminal.Gui/Views/Autocomplete/PopupAutocomplete.PopUp.cs b/Terminal.Gui/Views/Autocomplete/PopupAutocomplete.PopUp.cs index b7a67726e..18bd89b52 100644 --- a/Terminal.Gui/Views/Autocomplete/PopupAutocomplete.PopUp.cs +++ b/Terminal.Gui/Views/Autocomplete/PopupAutocomplete.PopUp.cs @@ -1,4 +1,3 @@ -#nullable enable namespace Terminal.Gui.Views; diff --git a/Terminal.Gui/Views/Autocomplete/PopupAutocomplete.cs b/Terminal.Gui/Views/Autocomplete/PopupAutocomplete.cs index 602a849d7..87f39fc2b 100644 --- a/Terminal.Gui/Views/Autocomplete/PopupAutocomplete.cs +++ b/Terminal.Gui/Views/Autocomplete/PopupAutocomplete.cs @@ -1,5 +1,4 @@ -using System.Diagnostics; - +#nullable disable namespace Terminal.Gui.Views; /// @@ -125,7 +124,7 @@ public abstract partial class PopupAutocomplete : AutocompleteBase { Visible = true; HostControl?.SetNeedsDraw (); - Application.Mouse.UngrabMouse (); + HostControl?.App?.Mouse.UngrabMouse (); return false; } @@ -137,7 +136,7 @@ public abstract partial class PopupAutocomplete : AutocompleteBase _closed = false; } - HostControl?.SetNeedsDraw (); + HostControl.SetNeedsDraw (); return false; } @@ -406,7 +405,7 @@ public abstract partial class PopupAutocomplete : AutocompleteBase string text = TextFormatter.ClipOrPad (toRender [i].Title, width); - Application.Driver?.AddStr (text); + _popup.App?.Driver?.AddStr (text); } } @@ -544,7 +543,6 @@ public abstract partial class PopupAutocomplete : AutocompleteBase /// protected abstract void SetCursorPosition (int column); -#nullable enable private Point? LastPopupPos { get; set; } #nullable restore diff --git a/Terminal.Gui/Views/Autocomplete/SingleWordSuggestionGenerator.cs b/Terminal.Gui/Views/Autocomplete/SingleWordSuggestionGenerator.cs index 1accda922..59bde624b 100644 --- a/Terminal.Gui/Views/Autocomplete/SingleWordSuggestionGenerator.cs +++ b/Terminal.Gui/Views/Autocomplete/SingleWordSuggestionGenerator.cs @@ -1,3 +1,4 @@ +#nullable disable namespace Terminal.Gui.Views; /// diff --git a/Terminal.Gui/Views/Autocomplete/Suggestion.cs b/Terminal.Gui/Views/Autocomplete/Suggestion.cs index 7b7bbd12a..a1facdc38 100644 --- a/Terminal.Gui/Views/Autocomplete/Suggestion.cs +++ b/Terminal.Gui/Views/Autocomplete/Suggestion.cs @@ -1,3 +1,4 @@ +#nullable disable namespace Terminal.Gui.Views; /// A replacement suggestion made by diff --git a/Terminal.Gui/Views/Bar.cs b/Terminal.Gui/Views/Bar.cs index 55e4a7914..3c04a16dc 100644 --- a/Terminal.Gui/Views/Bar.cs +++ b/Terminal.Gui/Views/Bar.cs @@ -1,5 +1,3 @@ -#nullable enable - namespace Terminal.Gui.Views; /// @@ -33,13 +31,14 @@ public class Bar : View, IOrientation, IDesignable // Initialized += Bar_Initialized; MouseEvent += OnMouseEvent; - - if (shortcuts is { }) + if (shortcuts is null) { - foreach (View shortcut in shortcuts) - { - Add (shortcut); - } + return; + } + + foreach (View shortcut in shortcuts) + { + base.Add (shortcut); } } @@ -107,7 +106,7 @@ public class Bar : View, IOrientation, IDesignable public void OnOrientationChanged (Orientation newOrientation) { // BUGBUG: this should not be SuperView.GetContentSize - LayoutBarItems (SuperView?.GetContentSize () ?? Application.Screen.Size); + LayoutBarItems (SuperView?.GetContentSize () ?? App?.Screen.Size ?? Size.Empty); } #endregion @@ -245,7 +244,7 @@ public class Bar : View, IOrientation, IDesignable barItem.X = 0; scBarItem.MinimumKeyTextSize = minKeyWidth; scBarItem.Width = scBarItem.GetWidthDimAuto (); - barItem.Layout (Application.Screen.Size); + barItem.Layout (App?.Screen.Size ?? Size.Empty); maxBarItemWidth = Math.Max (maxBarItemWidth, barItem.Frame.Width); } diff --git a/Terminal.Gui/Views/Button.cs b/Terminal.Gui/Views/Button.cs index 942e97b11..626af1852 100644 --- a/Terminal.Gui/Views/Button.cs +++ b/Terminal.Gui/Views/Button.cs @@ -1,4 +1,5 @@ +#nullable disable namespace Terminal.Gui.Views; /// diff --git a/Terminal.Gui/Views/CharMap/CharMap.cs b/Terminal.Gui/Views/CharMap/CharMap.cs index 02308ac1e..bf54252cf 100644 --- a/Terminal.Gui/Views/CharMap/CharMap.cs +++ b/Terminal.Gui/Views/CharMap/CharMap.cs @@ -1,4 +1,3 @@ -#nullable enable using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Globalization; @@ -384,12 +383,12 @@ public class CharMap : View, IDesignable try { decResponse = await client.GetCodepointDec (SelectedCodePoint).ConfigureAwait (false); - Application.Invoke (() => waitIndicator.RequestStop ()); + Application.Invoke ((_) => waitIndicator.RequestStop ()); } catch (HttpRequestException e) { getCodePointError = errorLabel.Text = e.Message; - Application.Invoke (() => waitIndicator.RequestStop ()); + Application.Invoke ((_) => waitIndicator.RequestStop ()); } }; Application.Run (waitIndicator); @@ -972,7 +971,7 @@ public class CharMap : View, IDesignable // Registering with the PopoverManager will ensure that the context menu is closed when the view is no longer focused // and the context menu is disposed when it is closed. - Application.Popover?.Register (contextMenu); + App!.Popover?.Register (contextMenu); contextMenu?.MakeVisible (ViewportToScreen (GetCursor (SelectedCodePoint))); diff --git a/Terminal.Gui/Views/CharMap/UcdApiClient.cs b/Terminal.Gui/Views/CharMap/UcdApiClient.cs index ae0af4838..a5abaf994 100644 --- a/Terminal.Gui/Views/CharMap/UcdApiClient.cs +++ b/Terminal.Gui/Views/CharMap/UcdApiClient.cs @@ -1,4 +1,3 @@ -#nullable enable namespace Terminal.Gui.Views; /// diff --git a/Terminal.Gui/Views/CharMap/UnicodeRange.cs b/Terminal.Gui/Views/CharMap/UnicodeRange.cs index 24f7378b2..4904d1751 100644 --- a/Terminal.Gui/Views/CharMap/UnicodeRange.cs +++ b/Terminal.Gui/Views/CharMap/UnicodeRange.cs @@ -1,4 +1,3 @@ -#nullable enable using System.Reflection; using System.Text.Unicode; diff --git a/Terminal.Gui/Views/CheckBox.cs b/Terminal.Gui/Views/CheckBox.cs index d5801f9f8..2a0535469 100644 --- a/Terminal.Gui/Views/CheckBox.cs +++ b/Terminal.Gui/Views/CheckBox.cs @@ -1,4 +1,4 @@ -#nullable enable + namespace Terminal.Gui.Views; diff --git a/Terminal.Gui/Views/CheckState.cs b/Terminal.Gui/Views/CheckState.cs index 2b001ddea..494df666b 100644 --- a/Terminal.Gui/Views/CheckState.cs +++ b/Terminal.Gui/Views/CheckState.cs @@ -1,4 +1,4 @@ -#nullable enable + namespace Terminal.Gui.Views; /// diff --git a/Terminal.Gui/Views/CollectionNavigation/CollectionNavigator.cs b/Terminal.Gui/Views/CollectionNavigation/CollectionNavigator.cs index a12d769c5..c0eb7a312 100644 --- a/Terminal.Gui/Views/CollectionNavigation/CollectionNavigator.cs +++ b/Terminal.Gui/Views/CollectionNavigation/CollectionNavigator.cs @@ -1,3 +1,4 @@ +#nullable disable using System.Collections; namespace Terminal.Gui.Views; @@ -21,4 +22,4 @@ internal class CollectionNavigator : CollectionNavigatorBase, IListCollectionNav /// protected override int GetCollectionLength () { return Collection.Count; } -} \ No newline at end of file +} diff --git a/Terminal.Gui/Views/CollectionNavigation/CollectionNavigatorBase.cs b/Terminal.Gui/Views/CollectionNavigation/CollectionNavigatorBase.cs index 274d32622..39234b10b 100644 --- a/Terminal.Gui/Views/CollectionNavigation/CollectionNavigatorBase.cs +++ b/Terminal.Gui/Views/CollectionNavigation/CollectionNavigatorBase.cs @@ -1,4 +1,4 @@ -#nullable enable + namespace Terminal.Gui.Views; @@ -202,4 +202,4 @@ internal abstract class CollectionNavigatorBase : ICollectionNavigator SearchString = ""; _lastKeystroke = DateTime.Now; } -} \ No newline at end of file +} diff --git a/Terminal.Gui/Views/CollectionNavigation/DefaultCollectionNavigatorMatcher.cs b/Terminal.Gui/Views/CollectionNavigation/DefaultCollectionNavigatorMatcher.cs index 20bee6809..ed910a828 100644 --- a/Terminal.Gui/Views/CollectionNavigation/DefaultCollectionNavigatorMatcher.cs +++ b/Terminal.Gui/Views/CollectionNavigation/DefaultCollectionNavigatorMatcher.cs @@ -1,4 +1,4 @@ -#nullable enable + namespace Terminal.Gui.Views; diff --git a/Terminal.Gui/Views/CollectionNavigation/ICollectionNavigator.cs b/Terminal.Gui/Views/CollectionNavigation/ICollectionNavigator.cs index 85a68d300..5ea621646 100644 --- a/Terminal.Gui/Views/CollectionNavigation/ICollectionNavigator.cs +++ b/Terminal.Gui/Views/CollectionNavigation/ICollectionNavigator.cs @@ -1,4 +1,4 @@ -#nullable enable + namespace Terminal.Gui.Views; diff --git a/Terminal.Gui/Views/CollectionNavigation/ICollectionNavigatorMatcher.cs b/Terminal.Gui/Views/CollectionNavigation/ICollectionNavigatorMatcher.cs index f45b59c0f..420c49674 100644 --- a/Terminal.Gui/Views/CollectionNavigation/ICollectionNavigatorMatcher.cs +++ b/Terminal.Gui/Views/CollectionNavigation/ICollectionNavigatorMatcher.cs @@ -1,3 +1,4 @@ +#nullable disable  namespace Terminal.Gui.Views; diff --git a/Terminal.Gui/Views/CollectionNavigation/IListCollectionNavigator.cs b/Terminal.Gui/Views/CollectionNavigation/IListCollectionNavigator.cs index f89e3f7c4..b382bc627 100644 --- a/Terminal.Gui/Views/CollectionNavigation/IListCollectionNavigator.cs +++ b/Terminal.Gui/Views/CollectionNavigation/IListCollectionNavigator.cs @@ -1,3 +1,4 @@ +#nullable disable using System.Collections; namespace Terminal.Gui.Views; diff --git a/Terminal.Gui/Views/CollectionNavigation/TableCollectionNavigator.cs b/Terminal.Gui/Views/CollectionNavigation/TableCollectionNavigator.cs index 69a817e50..21e3ce7d1 100644 --- a/Terminal.Gui/Views/CollectionNavigation/TableCollectionNavigator.cs +++ b/Terminal.Gui/Views/CollectionNavigation/TableCollectionNavigator.cs @@ -1,3 +1,4 @@ +#nullable disable namespace Terminal.Gui.Views; /// Collection navigator for cycling selections in a . diff --git a/Terminal.Gui/Views/Color/BBar.cs b/Terminal.Gui/Views/Color/BBar.cs index b59b5eecb..11c5cc9df 100644 --- a/Terminal.Gui/Views/Color/BBar.cs +++ b/Terminal.Gui/Views/Color/BBar.cs @@ -1,4 +1,4 @@ -#nullable enable + using ColorHelper; diff --git a/Terminal.Gui/Views/Color/ColorBar.cs b/Terminal.Gui/Views/Color/ColorBar.cs index ad15e231f..2be624be5 100644 --- a/Terminal.Gui/Views/Color/ColorBar.cs +++ b/Terminal.Gui/Views/Color/ColorBar.cs @@ -1,4 +1,4 @@ -#nullable enable + using ColorHelper; diff --git a/Terminal.Gui/Views/Color/ColorModelStrategy.cs b/Terminal.Gui/Views/Color/ColorModelStrategy.cs index eb3f15608..0f803be18 100644 --- a/Terminal.Gui/Views/Color/ColorModelStrategy.cs +++ b/Terminal.Gui/Views/Color/ColorModelStrategy.cs @@ -1,4 +1,4 @@ -#nullable enable + using ColorHelper; using ColorConverter = ColorHelper.ColorConverter; diff --git a/Terminal.Gui/Views/Color/ColorPicker.16.cs b/Terminal.Gui/Views/Color/ColorPicker.16.cs index 8c76f2648..6c1eda4cb 100644 --- a/Terminal.Gui/Views/Color/ColorPicker.16.cs +++ b/Terminal.Gui/Views/Color/ColorPicker.16.cs @@ -1,4 +1,4 @@ -#nullable enable + namespace Terminal.Gui.Views; diff --git a/Terminal.Gui/Views/Color/ColorPicker.Prompt.cs b/Terminal.Gui/Views/Color/ColorPicker.Prompt.cs index e6c29172c..400040e63 100644 --- a/Terminal.Gui/Views/Color/ColorPicker.Prompt.cs +++ b/Terminal.Gui/Views/Color/ColorPicker.Prompt.cs @@ -1,3 +1,4 @@ +#nullable disable  namespace Terminal.Gui.Views; diff --git a/Terminal.Gui/Views/Color/ColorPicker.Style.cs b/Terminal.Gui/Views/Color/ColorPicker.Style.cs index 616687156..afab89d5a 100644 --- a/Terminal.Gui/Views/Color/ColorPicker.Style.cs +++ b/Terminal.Gui/Views/Color/ColorPicker.Style.cs @@ -1,4 +1,4 @@ -#nullable enable + namespace Terminal.Gui.Views; diff --git a/Terminal.Gui/Views/Color/ColorPicker.cs b/Terminal.Gui/Views/Color/ColorPicker.cs index 2a60e536b..f0cdc76b9 100644 --- a/Terminal.Gui/Views/Color/ColorPicker.cs +++ b/Terminal.Gui/Views/Color/ColorPicker.cs @@ -1,4 +1,4 @@ -#nullable enable + namespace Terminal.Gui.Views; diff --git a/Terminal.Gui/Views/Color/GBar.cs b/Terminal.Gui/Views/Color/GBar.cs index ac9a7227f..b9dd5b435 100644 --- a/Terminal.Gui/Views/Color/GBar.cs +++ b/Terminal.Gui/Views/Color/GBar.cs @@ -1,4 +1,4 @@ -#nullable enable + using ColorHelper; diff --git a/Terminal.Gui/Views/Color/HueBar.cs b/Terminal.Gui/Views/Color/HueBar.cs index 9f7d29e45..a0b0b554d 100644 --- a/Terminal.Gui/Views/Color/HueBar.cs +++ b/Terminal.Gui/Views/Color/HueBar.cs @@ -1,4 +1,4 @@ -#nullable enable + using ColorHelper; using ColorConverter = ColorHelper.ColorConverter; diff --git a/Terminal.Gui/Views/Color/IColorBar.cs b/Terminal.Gui/Views/Color/IColorBar.cs index b8139b8d5..176edead9 100644 --- a/Terminal.Gui/Views/Color/IColorBar.cs +++ b/Terminal.Gui/Views/Color/IColorBar.cs @@ -1,3 +1,4 @@ +#nullable disable namespace Terminal.Gui.Views; internal interface IColorBar diff --git a/Terminal.Gui/Views/Color/LightnessBar.cs b/Terminal.Gui/Views/Color/LightnessBar.cs index 0f176a3f6..f3d1e3942 100644 --- a/Terminal.Gui/Views/Color/LightnessBar.cs +++ b/Terminal.Gui/Views/Color/LightnessBar.cs @@ -1,4 +1,4 @@ -#nullable enable + using ColorHelper; using ColorConverter = ColorHelper.ColorConverter; diff --git a/Terminal.Gui/Views/Color/RBar.cs b/Terminal.Gui/Views/Color/RBar.cs index 2610c66bb..e71dd9246 100644 --- a/Terminal.Gui/Views/Color/RBar.cs +++ b/Terminal.Gui/Views/Color/RBar.cs @@ -1,4 +1,4 @@ -#nullable enable + using ColorHelper; diff --git a/Terminal.Gui/Views/Color/SaturationBar.cs b/Terminal.Gui/Views/Color/SaturationBar.cs index 76fcd2029..9be7ab7f7 100644 --- a/Terminal.Gui/Views/Color/SaturationBar.cs +++ b/Terminal.Gui/Views/Color/SaturationBar.cs @@ -1,4 +1,4 @@ -#nullable enable + using ColorHelper; using ColorConverter = ColorHelper.ColorConverter; diff --git a/Terminal.Gui/Views/Color/ValueBar.cs b/Terminal.Gui/Views/Color/ValueBar.cs index 6352c7cab..5673ce8db 100644 --- a/Terminal.Gui/Views/Color/ValueBar.cs +++ b/Terminal.Gui/Views/Color/ValueBar.cs @@ -1,4 +1,4 @@ -#nullable enable + using ColorHelper; using ColorConverter = ColorHelper.ColorConverter; diff --git a/Terminal.Gui/Views/ComboBox.cs b/Terminal.Gui/Views/ComboBox.cs index 6da8acaea..86c4f4f79 100644 --- a/Terminal.Gui/Views/ComboBox.cs +++ b/Terminal.Gui/Views/ComboBox.cs @@ -1,3 +1,4 @@ +#nullable disable // // ComboBox.cs: ComboBox control // diff --git a/Terminal.Gui/Views/DatePicker.cs b/Terminal.Gui/Views/DatePicker.cs index a4f2791af..579607d3d 100644 --- a/Terminal.Gui/Views/DatePicker.cs +++ b/Terminal.Gui/Views/DatePicker.cs @@ -1,4 +1,4 @@ -#nullable enable + // // DatePicker.cs: DatePicker control diff --git a/Terminal.Gui/Views/Dialog.cs b/Terminal.Gui/Views/Dialog.cs index bf5379f36..4037d7312 100644 --- a/Terminal.Gui/Views/Dialog.cs +++ b/Terminal.Gui/Views/Dialog.cs @@ -1,4 +1,4 @@ -#nullable enable + namespace Terminal.Gui.Views; /// @@ -8,7 +8,7 @@ namespace Terminal.Gui.Views; /// /// /// To run the modally, create the , and pass it to -/// . This will execute the dialog until +/// . This will execute the dialog until /// it terminates via the (`Esc` by default), /// or when one of the views or buttons added to the dialog calls /// . @@ -21,7 +21,7 @@ public class Dialog : Window /// /// By default, , , , and are /// set - /// such that the will be centered in, and no larger than 90% of , if + /// such that the will be centered in, and no larger than 90% of , if /// there is one. Otherwise, /// it will be bound by the screen dimensions. /// diff --git a/Terminal.Gui/Views/FileDialog.cs b/Terminal.Gui/Views/FileDialog.cs index 0519ecba6..644cc55d9 100644 --- a/Terminal.Gui/Views/FileDialog.cs +++ b/Terminal.Gui/Views/FileDialog.cs @@ -1 +1,2 @@ - \ No newline at end of file +#nullable disable + diff --git a/Terminal.Gui/Views/FileDialogs/AllowedType.cs b/Terminal.Gui/Views/FileDialogs/AllowedType.cs index 8460edadf..2bcdfbe4a 100644 --- a/Terminal.Gui/Views/FileDialogs/AllowedType.cs +++ b/Terminal.Gui/Views/FileDialogs/AllowedType.cs @@ -1,3 +1,4 @@ +#nullable disable  namespace Terminal.Gui.Views; diff --git a/Terminal.Gui/Views/FileDialogs/DefaultFileOperations.cs b/Terminal.Gui/Views/FileDialogs/DefaultFileOperations.cs index 5f0aefcad..467e8d74c 100644 --- a/Terminal.Gui/Views/FileDialogs/DefaultFileOperations.cs +++ b/Terminal.Gui/Views/FileDialogs/DefaultFileOperations.cs @@ -1,3 +1,4 @@ +#nullable disable using System.IO.Abstractions; namespace Terminal.Gui.Views; diff --git a/Terminal.Gui/Views/FileDialogs/FileDialog.cs b/Terminal.Gui/Views/FileDialogs/FileDialog.cs index 1d2a3533f..4650730aa 100644 --- a/Terminal.Gui/Views/FileDialogs/FileDialog.cs +++ b/Terminal.Gui/Views/FileDialogs/FileDialog.cs @@ -1,4 +1,3 @@ -#nullable enable using System.IO.Abstractions; using System.Text.RegularExpressions; @@ -827,7 +826,7 @@ public class FileDialog : Dialog, IDesignable return _tableView.GetScheme (); } - Color color = Style.ColorProvider.GetColor (stats.FileSystemInfo) ?? new Color (Color.White); + Color color = Style.ColorProvider.GetColor (stats.FileSystemInfo!) ?? new Color (Color.White); var black = new Color (Color.Black); // TODO: Add some kind of cache for this @@ -1034,7 +1033,7 @@ public class FileDialog : Dialog, IDesignable private void New () { { - IFileSystemInfo created = FileOperationsHandler.New (_fileSystem, State!.Directory); + IFileSystemInfo created = FileOperationsHandler.New (_fileSystem!, State!.Directory); if (created is { }) { @@ -1175,7 +1174,7 @@ public class FileDialog : Dialog, IDesignable if (toRename?.Length == 1) { - IFileSystemInfo newNamed = FileOperationsHandler.Rename (_fileSystem, toRename.Single ()); + IFileSystemInfo newNamed = FileOperationsHandler.Rename (_fileSystem!, toRename.Single ()); if (newNamed is { }) { @@ -1233,7 +1232,7 @@ public class FileDialog : Dialog, IDesignable // Registering with the PopoverManager will ensure that the context menu is closed when the view is no longer focused // and the context menu is disposed when it is closed. - Application.Popover?.Register (contextMenu); + App!.Popover?.Register (contextMenu); contextMenu?.MakeVisible (e.ScreenPosition); } @@ -1261,7 +1260,7 @@ public class FileDialog : Dialog, IDesignable // Registering with the PopoverManager will ensure that the context menu is closed when the view is no longer focused // and the context menu is disposed when it is closed. - Application.Popover?.Register (contextMenu); + App!.Popover?.Register (contextMenu); contextMenu?.MakeVisible (e.ScreenPosition); } @@ -1569,7 +1568,7 @@ public class FileDialog : Dialog, IDesignable } } - if (Parent.SearchMatcher.IsMatch (f.FileSystemInfo)) + if (Parent.SearchMatcher.IsMatch (f.FileSystemInfo!)) { lock (_oLockFound) { @@ -1612,7 +1611,7 @@ public class FileDialog : Dialog, IDesignable UpdateChildrenToFound (); } - Application.Invoke (() => { Parent._spinnerView.Visible = false; }); + Application.Invoke ((_) => { Parent._spinnerView.Visible = false; }); } } @@ -1624,7 +1623,7 @@ public class FileDialog : Dialog, IDesignable } Application.Invoke ( - () => + (_) => { Parent._tbPath.Autocomplete.GenerateSuggestions ( new AutocompleteFilepathContext ( diff --git a/Terminal.Gui/Views/FileDialogs/FileDialogCollectionNavigator.cs b/Terminal.Gui/Views/FileDialogs/FileDialogCollectionNavigator.cs index d482c0a89..9e0590b83 100644 --- a/Terminal.Gui/Views/FileDialogs/FileDialogCollectionNavigator.cs +++ b/Terminal.Gui/Views/FileDialogs/FileDialogCollectionNavigator.cs @@ -1,3 +1,4 @@ +#nullable disable namespace Terminal.Gui.Views; internal class FileDialogCollectionNavigator (FileDialog fileDialog, TableView tableView) : CollectionNavigatorBase diff --git a/Terminal.Gui/Views/FileDialogs/FileDialogHistory.cs b/Terminal.Gui/Views/FileDialogs/FileDialogHistory.cs index 6558d40d9..535a19777 100644 --- a/Terminal.Gui/Views/FileDialogs/FileDialogHistory.cs +++ b/Terminal.Gui/Views/FileDialogs/FileDialogHistory.cs @@ -1,3 +1,4 @@ +#nullable disable using System.IO.Abstractions; namespace Terminal.Gui.Views; diff --git a/Terminal.Gui/Views/FileDialogs/FileDialogState.cs b/Terminal.Gui/Views/FileDialogs/FileDialogState.cs index 26cde6a62..aec681817 100644 --- a/Terminal.Gui/Views/FileDialogs/FileDialogState.cs +++ b/Terminal.Gui/Views/FileDialogs/FileDialogState.cs @@ -1,3 +1,4 @@ +#nullable disable using System.IO.Abstractions; namespace Terminal.Gui.Views; diff --git a/Terminal.Gui/Views/FileDialogs/FileDialogStyle.cs b/Terminal.Gui/Views/FileDialogs/FileDialogStyle.cs index 0ee7ac5aa..c72f24d8a 100644 --- a/Terminal.Gui/Views/FileDialogs/FileDialogStyle.cs +++ b/Terminal.Gui/Views/FileDialogs/FileDialogStyle.cs @@ -1,3 +1,4 @@ +#nullable disable using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.IO.Abstractions; diff --git a/Terminal.Gui/Views/FileDialogs/FileDialogTableSource.cs b/Terminal.Gui/Views/FileDialogs/FileDialogTableSource.cs index 14d37f097..bf15d3bac 100644 --- a/Terminal.Gui/Views/FileDialogs/FileDialogTableSource.cs +++ b/Terminal.Gui/Views/FileDialogs/FileDialogTableSource.cs @@ -1,4 +1,4 @@ -#nullable enable + namespace Terminal.Gui.Views; internal class FileDialogTableSource ( diff --git a/Terminal.Gui/Views/FileDialogs/FilesSelectedEventArgs.cs b/Terminal.Gui/Views/FileDialogs/FilesSelectedEventArgs.cs index 56ba5f1fe..f8629e31a 100644 --- a/Terminal.Gui/Views/FileDialogs/FilesSelectedEventArgs.cs +++ b/Terminal.Gui/Views/FileDialogs/FilesSelectedEventArgs.cs @@ -1,3 +1,4 @@ +#nullable disable  namespace Terminal.Gui.Views; diff --git a/Terminal.Gui/Views/FileDialogs/OpenDialog.cs b/Terminal.Gui/Views/FileDialogs/OpenDialog.cs index 5f916b1de..0940436d2 100644 --- a/Terminal.Gui/Views/FileDialogs/OpenDialog.cs +++ b/Terminal.Gui/Views/FileDialogs/OpenDialog.cs @@ -1,3 +1,4 @@ +#nullable disable // // FileDialog.cs: File system dialogs for open and save // @@ -22,7 +23,7 @@ namespace Terminal.Gui.Views; /// /// /// To use, create an instance of , and pass it to -/// . This will run the dialog modally, and when this returns, +/// . This will run the dialog modally, and when this returns, /// the list of files will be available on the property. /// /// To select more than one file, users can use the spacebar, or control-t. diff --git a/Terminal.Gui/Views/FileDialogs/OpenMode.cs b/Terminal.Gui/Views/FileDialogs/OpenMode.cs index e50370ea2..e3e62d60f 100644 --- a/Terminal.Gui/Views/FileDialogs/OpenMode.cs +++ b/Terminal.Gui/Views/FileDialogs/OpenMode.cs @@ -1,3 +1,4 @@ +#nullable disable namespace Terminal.Gui.Views; /// Determine which type to open. diff --git a/Terminal.Gui/Views/FileDialogs/SaveDialog.cs b/Terminal.Gui/Views/FileDialogs/SaveDialog.cs index 7646817f8..6e0da27e2 100644 --- a/Terminal.Gui/Views/FileDialogs/SaveDialog.cs +++ b/Terminal.Gui/Views/FileDialogs/SaveDialog.cs @@ -1,3 +1,4 @@ +#nullable disable // // FileDialog.cs: File system dialogs for open and save // @@ -17,7 +18,7 @@ namespace Terminal.Gui.Views; /// /// /// To use, create an instance of , and pass it to -/// . This will run the dialog modally, and when this returns, +/// . This will run the dialog modally, and when this returns, /// the property will contain the selected file name or null if the user canceled. /// /// diff --git a/Terminal.Gui/Views/FrameView.cs b/Terminal.Gui/Views/FrameView.cs index 4f8713c8b..cd99731b9 100644 --- a/Terminal.Gui/Views/FrameView.cs +++ b/Terminal.Gui/Views/FrameView.cs @@ -1,4 +1,4 @@ -#nullable enable + namespace Terminal.Gui.Views; diff --git a/Terminal.Gui/Views/GraphView/Axis.cs b/Terminal.Gui/Views/GraphView/Axis.cs index 3160f3ee3..177500f9f 100644 --- a/Terminal.Gui/Views/GraphView/Axis.cs +++ b/Terminal.Gui/Views/GraphView/Axis.cs @@ -1,3 +1,4 @@ +#nullable disable  namespace Terminal.Gui.Views; diff --git a/Terminal.Gui/Views/GraphView/BarSeriesBar.cs b/Terminal.Gui/Views/GraphView/BarSeriesBar.cs index efe17b59c..29f401e85 100644 --- a/Terminal.Gui/Views/GraphView/BarSeriesBar.cs +++ b/Terminal.Gui/Views/GraphView/BarSeriesBar.cs @@ -1,3 +1,4 @@ +#nullable disable namespace Terminal.Gui.Views; /// A single bar in a diff --git a/Terminal.Gui/Views/GraphView/GraphCellToRender.cs b/Terminal.Gui/Views/GraphView/GraphCellToRender.cs index 8bf4d99e9..db1b56e5c 100644 --- a/Terminal.Gui/Views/GraphView/GraphCellToRender.cs +++ b/Terminal.Gui/Views/GraphView/GraphCellToRender.cs @@ -1,3 +1,4 @@ +#nullable disable namespace Terminal.Gui.Views; /// diff --git a/Terminal.Gui/Views/GraphView/GraphView.cs b/Terminal.Gui/Views/GraphView/GraphView.cs index 92b31a60f..69f708da8 100644 --- a/Terminal.Gui/Views/GraphView/GraphView.cs +++ b/Terminal.Gui/Views/GraphView/GraphView.cs @@ -1,4 +1,4 @@ -#nullable enable + namespace Terminal.Gui.Views; @@ -8,6 +8,7 @@ public class GraphView : View, IDesignable /// Creates a new graph with a 1 to 1 graph space with absolute layout. public GraphView () { + App = ApplicationImpl.Instance; CanFocus = true; AxisX = new (); @@ -344,7 +345,7 @@ public class GraphView : View, IDesignable } /// - /// Sets the color attribute of to the (if defined) or + /// Sets the color attribute of to the (if defined) or /// otherwise. /// public void SetDriverColorToGraphColor () { SetAttribute (GraphColor ?? GetAttributeForRole (VisualRole.Normal)); } diff --git a/Terminal.Gui/Views/GraphView/IAnnotation.cs b/Terminal.Gui/Views/GraphView/IAnnotation.cs index 004339875..c396df59d 100644 --- a/Terminal.Gui/Views/GraphView/IAnnotation.cs +++ b/Terminal.Gui/Views/GraphView/IAnnotation.cs @@ -1,3 +1,4 @@ +#nullable disable namespace Terminal.Gui.Views; /// diff --git a/Terminal.Gui/Views/GraphView/LegendAnnotation.cs b/Terminal.Gui/Views/GraphView/LegendAnnotation.cs index 813228f7d..d8ac4be8b 100644 --- a/Terminal.Gui/Views/GraphView/LegendAnnotation.cs +++ b/Terminal.Gui/Views/GraphView/LegendAnnotation.cs @@ -1,3 +1,4 @@ +#nullable disable namespace Terminal.Gui.Views; /// diff --git a/Terminal.Gui/Views/GraphView/LineF.cs b/Terminal.Gui/Views/GraphView/LineF.cs index 67ef51cd1..462b03224 100644 --- a/Terminal.Gui/Views/GraphView/LineF.cs +++ b/Terminal.Gui/Views/GraphView/LineF.cs @@ -1,3 +1,4 @@ +#nullable disable namespace Terminal.Gui.Views; /// Describes two points in graph space and a line between them diff --git a/Terminal.Gui/Views/GraphView/PathAnnotation.cs b/Terminal.Gui/Views/GraphView/PathAnnotation.cs index 4a623f7df..622ea83a8 100644 --- a/Terminal.Gui/Views/GraphView/PathAnnotation.cs +++ b/Terminal.Gui/Views/GraphView/PathAnnotation.cs @@ -1,3 +1,4 @@ +#nullable disable namespace Terminal.Gui.Views; /// Sequence of lines to connect points e.g. of a diff --git a/Terminal.Gui/Views/GraphView/Series.cs b/Terminal.Gui/Views/GraphView/Series.cs index 187d6e6be..d417404ba 100644 --- a/Terminal.Gui/Views/GraphView/Series.cs +++ b/Terminal.Gui/Views/GraphView/Series.cs @@ -1,7 +1,6 @@ using System.Collections.ObjectModel; namespace Terminal.Gui.Views; -#nullable enable /// Describes a series of data that can be rendered into a > public interface ISeries { diff --git a/Terminal.Gui/Views/GraphView/TextAnnotation.cs b/Terminal.Gui/Views/GraphView/TextAnnotation.cs index 98f1fec3e..d1309fe9e 100644 --- a/Terminal.Gui/Views/GraphView/TextAnnotation.cs +++ b/Terminal.Gui/Views/GraphView/TextAnnotation.cs @@ -1,3 +1,4 @@ +#nullable disable namespace Terminal.Gui.Views; /// Displays text at a given position (in screen space or graph space) diff --git a/Terminal.Gui/Views/HexView.cs b/Terminal.Gui/Views/HexView.cs index cf645b03d..ee81eb832 100644 --- a/Terminal.Gui/Views/HexView.cs +++ b/Terminal.Gui/Views/HexView.cs @@ -1,4 +1,4 @@ -#nullable enable + // // HexView.cs: A hexadecimal viewer diff --git a/Terminal.Gui/Views/HexViewEventArgs.cs b/Terminal.Gui/Views/HexViewEventArgs.cs index 11e372115..2ee496b17 100644 --- a/Terminal.Gui/Views/HexViewEventArgs.cs +++ b/Terminal.Gui/Views/HexViewEventArgs.cs @@ -1,3 +1,4 @@ +#nullable disable // // HexView.cs: A hexadecimal viewer // diff --git a/Terminal.Gui/Views/IListDataSource.cs b/Terminal.Gui/Views/IListDataSource.cs index 37f1a63d6..76ab7e956 100644 --- a/Terminal.Gui/Views/IListDataSource.cs +++ b/Terminal.Gui/Views/IListDataSource.cs @@ -1,4 +1,4 @@ -#nullable enable +#nullable disable using System.Collections; using System.Collections.Specialized; diff --git a/Terminal.Gui/Views/Label.cs b/Terminal.Gui/Views/Label.cs index 5ea22dba9..c11347f9f 100644 --- a/Terminal.Gui/Views/Label.cs +++ b/Terminal.Gui/Views/Label.cs @@ -24,13 +24,13 @@ public class Label : View, IDesignable Width = Dim.Auto (DimAutoStyle.Text); // On HoKey, pass it to the next view - AddCommand (Command.HotKey, InvokeHotKeyOnNextPeer); + AddCommand (Command.HotKey, InvokeHotKeyOnNextPeer!); TitleChanged += Label_TitleChanged; MouseClick += Label_MouseClick; } - private void Label_MouseClick (object sender, MouseEventArgs e) + private void Label_MouseClick (object? sender, MouseEventArgs e) { if (!CanFocus) { @@ -38,7 +38,7 @@ public class Label : View, IDesignable } } - private void Label_TitleChanged (object sender, EventArgs e) + private void Label_TitleChanged (object? sender, EventArgs e) { base.Text = e.Value; TextFormatter.HotKeySpecifier = HotKeySpecifier; diff --git a/Terminal.Gui/Views/Line.cs b/Terminal.Gui/Views/Line.cs index 5b5a9c9a8..8da81b99d 100644 --- a/Terminal.Gui/Views/Line.cs +++ b/Terminal.Gui/Views/Line.cs @@ -1,5 +1,3 @@ -#nullable enable - namespace Terminal.Gui.Views; /// diff --git a/Terminal.Gui/Views/ListView.cs b/Terminal.Gui/Views/ListView.cs index b933af9e0..729e8066b 100644 --- a/Terminal.Gui/Views/ListView.cs +++ b/Terminal.Gui/Views/ListView.cs @@ -1,3 +1,4 @@ +#nullable disable using System.Collections; using System.Collections.ObjectModel; using System.Collections.Specialized; diff --git a/Terminal.Gui/Views/ListViewEventArgs.cs b/Terminal.Gui/Views/ListViewEventArgs.cs index fe83de580..7a718b536 100644 --- a/Terminal.Gui/Views/ListViewEventArgs.cs +++ b/Terminal.Gui/Views/ListViewEventArgs.cs @@ -1,3 +1,4 @@ +#nullable disable namespace Terminal.Gui.Views; /// for events. diff --git a/Terminal.Gui/Views/Menu/MenuBarItemv2.cs b/Terminal.Gui/Views/Menu/MenuBarItemv2.cs index ca82d7fc3..8d04c3e2b 100644 --- a/Terminal.Gui/Views/Menu/MenuBarItemv2.cs +++ b/Terminal.Gui/Views/Menu/MenuBarItemv2.cs @@ -1,6 +1,7 @@ -#nullable enable +using System.Diagnostics; + namespace Terminal.Gui.Views; /// @@ -113,6 +114,8 @@ public class MenuBarItemv2 : MenuItemv2 if (_popoverMenu is { }) { + _popoverMenu.App = App; + PopoverMenuOpen = _popoverMenu.Visible; _popoverMenu.VisibleChanged += OnPopoverVisibleChanged; _popoverMenu.Accepted += OnPopoverMenuOnAccepted; diff --git a/Terminal.Gui/Views/Menu/MenuBarv2.cs b/Terminal.Gui/Views/Menu/MenuBarv2.cs index fdf1333a2..746cdb44d 100644 --- a/Terminal.Gui/Views/Menu/MenuBarv2.cs +++ b/Terminal.Gui/Views/Menu/MenuBarv2.cs @@ -1,4 +1,4 @@ -#nullable enable + using System.ComponentModel; using System.Diagnostics; @@ -321,7 +321,7 @@ public class MenuBarv2 : Menuv2, IDesignable // TODO: This needs to be done whenever a menuitem in any MenuBarItem changes foreach (MenuBarItemv2? mbi in SubViews.Select (s => s as MenuBarItemv2)) { - Application.Popover?.Register (mbi?.PopoverMenu); + App?.Popover?.Register (mbi?.PopoverMenu); } } @@ -396,11 +396,11 @@ public class MenuBarv2 : Menuv2, IDesignable } // If the active Application Popover is part of this MenuBar, hide it. - if (Application.Popover?.GetActivePopover () is PopoverMenu popoverMenu + if (App?.Popover?.GetActivePopover () is PopoverMenu popoverMenu && popoverMenu.Root?.SuperMenuItem?.SuperView == this) { - // Logging.Debug ($"{Title} - Calling Application.Popover?.Hide ({popoverMenu.Title})"); - Application.Popover.Hide (popoverMenu); + // Logging.Debug ($"{Title} - Calling App?.Popover?.Hide ({popoverMenu.Title})"); + App?.Popover.Hide (popoverMenu); } if (menuBarItem is null) @@ -420,7 +420,11 @@ public class MenuBarv2 : Menuv2, IDesignable } // Logging.Debug ($"{Title} - \"{menuBarItem.PopoverMenu?.Title}\".MakeVisible"); - menuBarItem.PopoverMenu?.MakeVisible (new Point (menuBarItem.FrameToScreen ().X, menuBarItem.FrameToScreen ().Bottom)); + if (menuBarItem.PopoverMenu is { }) + { + menuBarItem.PopoverMenu.App ??= App; + menuBarItem.PopoverMenu.MakeVisible (new Point (menuBarItem.FrameToScreen ().X, menuBarItem.FrameToScreen ().Bottom)); + } menuBarItem.Accepting += OnMenuItemAccepted; @@ -501,11 +505,16 @@ public class MenuBarv2 : Menuv2, IDesignable } /// - public bool EnableForDesign (ref TContext context) where TContext : notnull + public bool EnableForDesign (ref TContext targetView) where TContext : notnull { // Note: This menu is used by unit tests. If you modify it, you'll likely have to update // unit tests. + if (targetView is View target) + { + App ??= target.App; + } + Id = "DemoBar"; var bordersCb = new CheckBox @@ -552,10 +561,10 @@ public class MenuBarv2 : Menuv2, IDesignable new MenuBarItemv2 ( "_File", [ - new MenuItemv2 (context as View, Command.New), - new MenuItemv2 (context as View, Command.Open), - new MenuItemv2 (context as View, Command.Save), - new MenuItemv2 (context as View, Command.SaveAs), + new MenuItemv2 (targetView as View, Command.New), + new MenuItemv2 (targetView as View, Command.Open), + new MenuItemv2 (targetView as View, Command.Save), + new MenuItemv2 (targetView as View, Command.SaveAs), new Line (), new MenuItemv2 { @@ -576,7 +585,7 @@ public class MenuBarv2 : Menuv2, IDesignable Key = Key.W.WithCtrl, CommandView = enableOverwriteCb, Command = Command.EnableOverwrite, - TargetView = context as View + TargetView = targetView as View }, new () { @@ -622,7 +631,7 @@ public class MenuBarv2 : Menuv2, IDesignable new Line (), new MenuItemv2 { - TargetView = context as View, + TargetView = targetView as View, Key = Application.QuitKey, Command = Command.Quit } @@ -634,11 +643,11 @@ public class MenuBarv2 : Menuv2, IDesignable new MenuBarItemv2 ( "_Edit", [ - new MenuItemv2 (context as View, Command.Cut), - new MenuItemv2 (context as View, Command.Copy), - new MenuItemv2 (context as View, Command.Paste), + new MenuItemv2 (targetView as View, Command.Cut), + new MenuItemv2 (targetView as View, Command.Copy), + new MenuItemv2 (targetView as View, Command.Paste), new Line (), - new MenuItemv2 (context as View, Command.SelectAll), + new MenuItemv2 (targetView as View, Command.SelectAll), new Line (), new MenuItemv2 { diff --git a/Terminal.Gui/Views/Menu/MenuItemv2.cs b/Terminal.Gui/Views/Menu/MenuItemv2.cs index c5f472307..7533881cd 100644 --- a/Terminal.Gui/Views/Menu/MenuItemv2.cs +++ b/Terminal.Gui/Views/Menu/MenuItemv2.cs @@ -1,4 +1,3 @@ -#nullable enable using System.ComponentModel; @@ -136,7 +135,7 @@ public class MenuItemv2 : Shortcut { // Is this an Application-bound command? // Logging.Debug ($"{Title} - Application.InvokeCommandsBoundToKey ({Key})..."); - ret = Application.InvokeCommandsBoundToKey (Key); + ret = App?.Keyboard.InvokeCommandsBoundToKey (Key); } } @@ -186,6 +185,7 @@ public class MenuItemv2 : Shortcut if (_subMenu is { }) { + SubMenu!.App ??= App; SubMenu!.Visible = false; // TODO: This is a temporary hack - add a flag or something instead KeyView.Text = $"{Glyphs.RightArrow}"; diff --git a/Terminal.Gui/Views/Menu/Menuv2.cs b/Terminal.Gui/Views/Menu/Menuv2.cs index 1eb5f6cc4..2cd610f41 100644 --- a/Terminal.Gui/Views/Menu/Menuv2.cs +++ b/Terminal.Gui/Views/Menu/Menuv2.cs @@ -1,4 +1,4 @@ -#nullable enable + namespace Terminal.Gui.Views; @@ -229,4 +229,4 @@ public class Menuv2 : Bar ConfigurationManager.Applied -= OnConfigurationManagerApplied; } } -} \ No newline at end of file +} diff --git a/Terminal.Gui/Views/Menu/PopoverMenu.cs b/Terminal.Gui/Views/Menu/PopoverMenu.cs index dcaef66ea..c04041bf5 100644 --- a/Terminal.Gui/Views/Menu/PopoverMenu.cs +++ b/Terminal.Gui/Views/Menu/PopoverMenu.cs @@ -1,4 +1,4 @@ -#nullable enable + namespace Terminal.Gui.Views; @@ -9,7 +9,7 @@ namespace Terminal.Gui.Views; /// /// /// -/// To use as a context menu, register the popover menu with and call +/// To use as a context menu, register the popover menu with and call /// . /// /// @@ -176,7 +176,7 @@ public class PopoverMenu : PopoverBaseImpl, IDesignable UpdateKeyBindings (); SetPosition (idealScreenPosition); - Application.Popover?.Show (this); + App!.Popover?.Show (this); } /// @@ -188,7 +188,7 @@ public class PopoverMenu : PopoverBaseImpl, IDesignable /// If , the current mouse position will be used. public void SetPosition (Point? idealScreenPosition = null) { - idealScreenPosition ??= Application.GetLastMousePosition (); + idealScreenPosition ??= App?.Mouse.LastMousePosition; if (idealScreenPosition is null || Root is null) { @@ -199,6 +199,7 @@ public class PopoverMenu : PopoverBaseImpl, IDesignable if (!Root.IsInitialized) { + Root.App ??= App; Root.BeginInit (); Root.EndInit (); Root.Layout (); @@ -223,7 +224,7 @@ public class PopoverMenu : PopoverBaseImpl, IDesignable else { HideAndRemoveSubMenu (_root); - Application.Popover?.Hide (this); + App?.Popover?.Hide (this); } } @@ -246,6 +247,11 @@ public class PopoverMenu : PopoverBaseImpl, IDesignable _root = value; + if (_root is { }) + { + _root.App = App; + } + // TODO: This needs to be done whenever any MenuItem in the menu tree changes to support dynamic menus // TODO: And it needs to clear the old bindings first UpdateKeyBindings (); @@ -255,6 +261,7 @@ public class PopoverMenu : PopoverBaseImpl, IDesignable foreach (Menuv2 menu in allMenus) { + menu.App = App; menu.Visible = false; menu.Accepting += MenuOnAccepting; menu.Accepted += MenuAccepted; @@ -279,7 +286,7 @@ public class PopoverMenu : PopoverBaseImpl, IDesignable else { // No TargetView implies Application HotKey - key = Application.KeyBindings.GetFirstFromCommands (menuItem.Command); + key = App?.Keyboard.KeyBindings.GetFirstFromCommands (menuItem.Command); } if (key is not { IsValid: true }) @@ -439,6 +446,7 @@ public class PopoverMenu : PopoverBaseImpl, IDesignable if (!menu!.IsInitialized) { + menu.App ??= App; menu.BeginInit (); menu.EndInit (); } @@ -626,27 +634,27 @@ public class PopoverMenu : PopoverBaseImpl, IDesignable } /// - public bool EnableForDesign (ref TContext context) where TContext : notnull + public bool EnableForDesign (ref TContext targetView) where TContext : notnull { // Note: This menu is used by unit tests. If you modify it, you'll likely have to update // unit tests. Root = new ( [ - new MenuItemv2 (context as View, Command.Cut), - new MenuItemv2 (context as View, Command.Copy), - new MenuItemv2 (context as View, Command.Paste), + new MenuItemv2 (targetView as View, Command.Cut), + new MenuItemv2 (targetView as View, Command.Copy), + new MenuItemv2 (targetView as View, Command.Paste), new Line (), - new MenuItemv2 (context as View, Command.SelectAll), + new MenuItemv2 (targetView as View, Command.SelectAll), new Line (), - new MenuItemv2 (context as View, Command.Quit) + new MenuItemv2 (targetView as View, Command.Quit) ]) { Title = "Popover Demo Root" }; // NOTE: This is a workaround for the fact that the PopoverMenu is not visible in the designer - // NOTE: without being activated via Application.Popover. But we want it to be visible. + // NOTE: without being activated via App?.Popover. But we want it to be visible. // NOTE: If you use PopoverView.EnableForDesign for real Popover scenarios, change back to false // NOTE: after calling EnableForDesign. //Visible = true; diff --git a/Terminal.Gui/Views/Menuv1/Menu.cs b/Terminal.Gui/Views/Menuv1/Menu.cs index 7833e4196..f538af7dd 100644 --- a/Terminal.Gui/Views/Menuv1/Menu.cs +++ b/Terminal.Gui/Views/Menuv1/Menu.cs @@ -1,5 +1,4 @@ #pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring as nullable. -#nullable enable namespace Terminal.Gui.Views; @@ -13,10 +12,10 @@ internal sealed class Menu : View { public Menu () { - if (Application.Top is { }) + if (Application.Current is { }) { - Application.Top.DrawComplete += Top_DrawComplete; - Application.Top.SizeChanging += Current_TerminalResized; + Application.Current.DrawComplete += Top_DrawComplete; + Application.Current.SizeChanging += Current_TerminalResized; } Application.MouseEvent += Application_RootMouseEvent; @@ -232,10 +231,10 @@ internal sealed class Menu : View { RemoveKeyBindingsHotKey (_barItems); - if (Application.Top is { }) + if (Application.Current is { }) { - Application.Top.DrawComplete -= Top_DrawComplete; - Application.Top.SizeChanging -= Current_TerminalResized; + Application.Current.DrawComplete -= Top_DrawComplete; + Application.Current.SizeChanging -= Current_TerminalResized; } Application.MouseEvent -= Application_RootMouseEvent; @@ -968,11 +967,11 @@ internal sealed class Menu : View // The -3 is left/right border + one space (not sure what for) tf.Draw ( - ViewportToScreen (new Rectangle (1, i, Frame.Width - 3, 1)), - i == _currentChild ? GetAttributeForRole (VisualRole.Focus) : GetAttributeForRole (VisualRole.Normal), - i == _currentChild ? GetAttributeForRole (VisualRole.HotFocus) : GetAttributeForRole (VisualRole.HotNormal), - SuperView?.ViewportToScreen (SuperView.Viewport) ?? Rectangle.Empty - ); + driver: Driver, + screen: ViewportToScreen (new Rectangle (1, i, Frame.Width - 3, 1)), + normalColor: i == _currentChild ? GetAttributeForRole (VisualRole.Focus) : GetAttributeForRole (VisualRole.Normal), + hotColor: i == _currentChild ? GetAttributeForRole (VisualRole.HotFocus) : GetAttributeForRole (VisualRole.HotNormal), + maximum: SuperView?.ViewportToScreen (SuperView.Viewport) ?? Rectangle.Empty); } else { diff --git a/Terminal.Gui/Views/Menuv1/MenuBar.cs b/Terminal.Gui/Views/Menuv1/MenuBar.cs index d2ab03dc5..78d1c3a29 100644 --- a/Terminal.Gui/Views/Menuv1/MenuBar.cs +++ b/Terminal.Gui/Views/Menuv1/MenuBar.cs @@ -1,4 +1,3 @@ -#nullable enable namespace Terminal.Gui.Views; @@ -422,7 +421,7 @@ public class MenuBar : View, IDesignable _selected = 0; SetNeedsDraw (); - _previousFocused = (SuperView is null ? Application.Top?.Focused : SuperView.Focused)!; + _previousFocused = (SuperView is null ? Application.Current?.Focused : SuperView.Focused)!; OpenMenu (_selected); if (!SelectEnabledItem ( @@ -491,7 +490,7 @@ public class MenuBar : View, IDesignable if (_openMenu is null) { - _previousFocused = (SuperView is null ? Application.Top?.Focused ?? null : SuperView.Focused)!; + _previousFocused = (SuperView is null ? Application.Current?.Focused ?? null : SuperView.Focused)!; } OpenMenu (idx, sIdx, subMenu); @@ -703,7 +702,7 @@ public class MenuBar : View, IDesignable } Rectangle superViewFrame = SuperView?.Frame ?? Application.Screen; - View? sv = SuperView ?? Application.Top; + View? sv = SuperView ?? Application.Current; if (sv is null) { @@ -835,7 +834,7 @@ public class MenuBar : View, IDesignable { case null: // Open a submenu below a MenuBar - _lastFocused ??= SuperView is null ? Application.Top?.MostFocused : SuperView.MostFocused; + _lastFocused ??= SuperView is null ? Application.Current?.MostFocused : SuperView.MostFocused; if (_openSubMenu is { } && !CloseMenu (false, true)) { @@ -1680,9 +1679,9 @@ public class MenuBar : View, IDesignable /// - public bool EnableForDesign (ref TContext context) where TContext : notnull + public bool EnableForDesign (ref TContext targetView) where TContext : notnull { - if (context is not Func actionFn) + if (targetView is not Func actionFn) { actionFn = (_) => true; } diff --git a/Terminal.Gui/Views/Menuv1/MenuBarItem.cs b/Terminal.Gui/Views/Menuv1/MenuBarItem.cs index 8b3be2116..fe96c1589 100644 --- a/Terminal.Gui/Views/Menuv1/MenuBarItem.cs +++ b/Terminal.Gui/Views/Menuv1/MenuBarItem.cs @@ -1,4 +1,3 @@ -#nullable enable namespace Terminal.Gui.Views; diff --git a/Terminal.Gui/Views/Menuv1/MenuClosingEventArgs.cs b/Terminal.Gui/Views/Menuv1/MenuClosingEventArgs.cs index 0a830c2b8..e223893c8 100644 --- a/Terminal.Gui/Views/Menuv1/MenuClosingEventArgs.cs +++ b/Terminal.Gui/Views/Menuv1/MenuClosingEventArgs.cs @@ -1,3 +1,4 @@ +#nullable disable namespace Terminal.Gui.Views; #pragma warning disable CS0618 // Type or member is obsolete @@ -30,4 +31,4 @@ public class MenuClosingEventArgs : EventArgs /// Indicates whether the current menu will reopen. public bool Reopen { get; } -} \ No newline at end of file +} diff --git a/Terminal.Gui/Views/Menuv1/MenuItem.cs b/Terminal.Gui/Views/Menuv1/MenuItem.cs index 002ea0a38..9d66c1c45 100644 --- a/Terminal.Gui/Views/Menuv1/MenuItem.cs +++ b/Terminal.Gui/Views/Menuv1/MenuItem.cs @@ -1,5 +1,4 @@ #pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring as nullable. -#nullable enable namespace Terminal.Gui.Views; diff --git a/Terminal.Gui/Views/Menuv1/MenuItemCheckStyle.cs b/Terminal.Gui/Views/Menuv1/MenuItemCheckStyle.cs index 87bc5168e..ad189e373 100644 --- a/Terminal.Gui/Views/Menuv1/MenuItemCheckStyle.cs +++ b/Terminal.Gui/Views/Menuv1/MenuItemCheckStyle.cs @@ -1,3 +1,4 @@ +#nullable disable namespace Terminal.Gui.Views; /// Specifies how a shows selection state. @@ -12,4 +13,4 @@ public enum MenuItemCheckStyle /// The menu item is part of a menu radio group (see ) and will indicate selected state. Radio = 0b_0000_0010 -} \ No newline at end of file +} diff --git a/Terminal.Gui/Views/Menuv1/MenuOpenedEventArgs.cs b/Terminal.Gui/Views/Menuv1/MenuOpenedEventArgs.cs index ba6c4adcf..581b9b04c 100644 --- a/Terminal.Gui/Views/Menuv1/MenuOpenedEventArgs.cs +++ b/Terminal.Gui/Views/Menuv1/MenuOpenedEventArgs.cs @@ -1,3 +1,4 @@ +#nullable disable namespace Terminal.Gui.Views; #pragma warning disable CS0618 // Type or member is obsolete @@ -18,4 +19,4 @@ public class MenuOpenedEventArgs : EventArgs /// The parent of . Will be null if menu opening is the root. public MenuBarItem Parent { get; } -} \ No newline at end of file +} diff --git a/Terminal.Gui/Views/Menuv1/MenuOpeningEventArgs.cs b/Terminal.Gui/Views/Menuv1/MenuOpeningEventArgs.cs index e19421caa..c2e10d6d4 100644 --- a/Terminal.Gui/Views/Menuv1/MenuOpeningEventArgs.cs +++ b/Terminal.Gui/Views/Menuv1/MenuOpeningEventArgs.cs @@ -1,3 +1,4 @@ +#nullable disable namespace Terminal.Gui.Views; #pragma warning disable CS0618 // Type or member is obsolete @@ -23,4 +24,4 @@ public class MenuOpeningEventArgs : EventArgs /// The new to be replaced. public MenuBarItem NewMenuBarItem { get; set; } -} \ No newline at end of file +} diff --git a/Terminal.Gui/Views/MessageBox.cs b/Terminal.Gui/Views/MessageBox.cs index 478d79565..bdbf323a0 100644 --- a/Terminal.Gui/Views/MessageBox.cs +++ b/Terminal.Gui/Views/MessageBox.cs @@ -1,3 +1,4 @@ +#nullable disable  namespace Terminal.Gui.Views; diff --git a/Terminal.Gui/Views/NumericUpDown.cs b/Terminal.Gui/Views/NumericUpDown.cs index b0066c363..adb94d554 100644 --- a/Terminal.Gui/Views/NumericUpDown.cs +++ b/Terminal.Gui/Views/NumericUpDown.cs @@ -1,4 +1,3 @@ -#nullable enable using System.ComponentModel; using System.Numerics; diff --git a/Terminal.Gui/Views/ProgressBar.cs b/Terminal.Gui/Views/ProgressBar.cs index 3e27bd556..b7cf3a2e0 100644 --- a/Terminal.Gui/Views/ProgressBar.cs +++ b/Terminal.Gui/Views/ProgressBar.cs @@ -1,4 +1,4 @@ -#nullable enable + namespace Terminal.Gui.Views; @@ -185,12 +185,12 @@ public class ProgressBar : View, IDesignable GetAttributeForRole (VisualRole.Active).Style); } - tf.Draw ( - ViewportToScreen (Viewport), - attr, - GetAttributeForRole (VisualRole.Normal), - SuperView?.ViewportToScreen (SuperView.Viewport) ?? default (Rectangle) - ); + tf.Draw ( + driver: Driver, + screen: ViewportToScreen (Viewport), + normalColor: attr, + hotColor: GetAttributeForRole (VisualRole.Normal), + maximum: SuperView?.ViewportToScreen (SuperView.Viewport) ?? default (Rectangle)); } return true; diff --git a/Terminal.Gui/Views/ReadOnlyCollectionExtensions.cs b/Terminal.Gui/Views/ReadOnlyCollectionExtensions.cs index 6eb572e29..0a615c92f 100644 --- a/Terminal.Gui/Views/ReadOnlyCollectionExtensions.cs +++ b/Terminal.Gui/Views/ReadOnlyCollectionExtensions.cs @@ -1,3 +1,4 @@ +#nullable disable namespace Terminal.Gui.Views; /// diff --git a/Terminal.Gui/Views/ScrollBar/ScrollBar.cs b/Terminal.Gui/Views/ScrollBar/ScrollBar.cs index e6b1eb590..4d7468434 100644 --- a/Terminal.Gui/Views/ScrollBar/ScrollBar.cs +++ b/Terminal.Gui/Views/ScrollBar/ScrollBar.cs @@ -1,4 +1,4 @@ -#nullable enable + using System.ComponentModel; diff --git a/Terminal.Gui/Views/ScrollBar/ScrollSlider.cs b/Terminal.Gui/Views/ScrollBar/ScrollSlider.cs index 920f88cce..d3d861ac2 100644 --- a/Terminal.Gui/Views/ScrollBar/ScrollSlider.cs +++ b/Terminal.Gui/Views/ScrollBar/ScrollSlider.cs @@ -1,4 +1,4 @@ -#nullable enable + using System.ComponentModel; diff --git a/Terminal.Gui/Views/SelectedItemChangedArgs.cs b/Terminal.Gui/Views/SelectedItemChangedArgs.cs index 691dea2f6..6d09f0d8f 100644 --- a/Terminal.Gui/Views/SelectedItemChangedArgs.cs +++ b/Terminal.Gui/Views/SelectedItemChangedArgs.cs @@ -1,4 +1,4 @@ -#nullable enable + namespace Terminal.Gui.Views; /// Event arguments for the SelectedItemChanged event. diff --git a/Terminal.Gui/Views/Selectors/FlagSelector.cs b/Terminal.Gui/Views/Selectors/FlagSelector.cs index dfc45456a..c2c8c15f9 100644 --- a/Terminal.Gui/Views/Selectors/FlagSelector.cs +++ b/Terminal.Gui/Views/Selectors/FlagSelector.cs @@ -1,7 +1,3 @@ -#nullable enable - -using System.Collections.Immutable; - namespace Terminal.Gui.Views; // DoubleClick - Focus, Select (Toggle), and Accept the item under the mouse. diff --git a/Terminal.Gui/Views/Selectors/FlagSelectorTEnum.cs b/Terminal.Gui/Views/Selectors/FlagSelectorTEnum.cs index 9ec066e02..86aa5c9a8 100644 --- a/Terminal.Gui/Views/Selectors/FlagSelectorTEnum.cs +++ b/Terminal.Gui/Views/Selectors/FlagSelectorTEnum.cs @@ -1,4 +1,3 @@ -#nullable enable using System; namespace Terminal.Gui.Views; diff --git a/Terminal.Gui/Views/Selectors/OptionSelector.cs b/Terminal.Gui/Views/Selectors/OptionSelector.cs index 48a9d3503..ab4159cc3 100644 --- a/Terminal.Gui/Views/Selectors/OptionSelector.cs +++ b/Terminal.Gui/Views/Selectors/OptionSelector.cs @@ -1,4 +1,3 @@ -#nullable enable using System.Collections.Immutable; using System.Diagnostics; diff --git a/Terminal.Gui/Views/Selectors/OptionSelectorTEnum.cs b/Terminal.Gui/Views/Selectors/OptionSelectorTEnum.cs index 604a6cf82..188d1fcc7 100644 --- a/Terminal.Gui/Views/Selectors/OptionSelectorTEnum.cs +++ b/Terminal.Gui/Views/Selectors/OptionSelectorTEnum.cs @@ -1,4 +1,3 @@ -#nullable enable namespace Terminal.Gui.Views; /// diff --git a/Terminal.Gui/Views/Selectors/SelectorBase.cs b/Terminal.Gui/Views/Selectors/SelectorBase.cs index 7bef55dd2..5747bf4b1 100644 --- a/Terminal.Gui/Views/Selectors/SelectorBase.cs +++ b/Terminal.Gui/Views/Selectors/SelectorBase.cs @@ -1,4 +1,3 @@ -#nullable enable using System.Collections.Immutable; namespace Terminal.Gui.Views; diff --git a/Terminal.Gui/Views/Selectors/SelectorStyles.cs b/Terminal.Gui/Views/Selectors/SelectorStyles.cs index c1087001d..cf0fad51c 100644 --- a/Terminal.Gui/Views/Selectors/SelectorStyles.cs +++ b/Terminal.Gui/Views/Selectors/SelectorStyles.cs @@ -1,4 +1,4 @@ -#nullable enable + namespace Terminal.Gui.Views; /// diff --git a/Terminal.Gui/Views/Shortcut.cs b/Terminal.Gui/Views/Shortcut.cs index ccccc01bb..a0b7c5683 100644 --- a/Terminal.Gui/Views/Shortcut.cs +++ b/Terminal.Gui/Views/Shortcut.cs @@ -1,5 +1,4 @@ -#nullable enable -using System.ComponentModel; +using System.Diagnostics; namespace Terminal.Gui.Views; @@ -25,7 +24,8 @@ namespace Terminal.Gui.Views; /// /// By default, a Shortcut displays the command text on the left side, the help text in the middle, and the key /// binding on the -/// right side. Set to to reverse the order. +/// right side. Set to to reverse the +/// order. /// /// /// The command text can be set by setting the 's Text property or by setting @@ -44,7 +44,7 @@ public class Shortcut : View, IOrientation, IDesignable /// /// Creates a new instance of . /// - public Shortcut () : this (Key.Empty, null, null, null) { } + public Shortcut () : this (Key.Empty, null, null) { } /// /// Creates a new instance of . @@ -60,7 +60,7 @@ public class Shortcut : View, IOrientation, IDesignable /// The help text to display. public Shortcut (Key key, string? commandText, Action? action, string? helpText = null) { - HighlightStates = ViewBase.MouseState.None; + HighlightStates = MouseState.None; CanFocus = true; if (Border is { }) @@ -88,10 +88,12 @@ public class Shortcut : View, IOrientation, IDesignable Title = commandText ?? string.Empty; HelpView.Id = "_helpView"; + //HelpView.CanFocus = false; HelpView.Text = helpText ?? string.Empty; KeyView.Id = "_keyView"; + //KeyView.CanFocus = false; key ??= Key.Empty; Key = key; @@ -101,13 +103,22 @@ public class Shortcut : View, IOrientation, IDesignable ShowHide (); } + /// + public override void EndInit () + { + base.EndInit (); + App ??= SuperView?.App; + Debug.Assert (App is { }); + UpdateKeyBindings (Key.Empty); + } + // Helper to set Width consistently internal Dim GetWidthDimAuto () { return Dim.Auto ( DimAutoStyle.Content, - minimumContentDim: Dim.Func (_ => _minimumNaturalWidth ?? 0), - maximumContentDim: Dim.Func (_ => _minimumNaturalWidth ?? 0))!; + Dim.Func (_ => _minimumNaturalWidth ?? 0), + Dim.Func (_ => _minimumNaturalWidth ?? 0))!; } private AlignmentModes _alignmentModes = AlignmentModes.StartToEnd | AlignmentModes.IgnoreFirstOrLast; @@ -168,24 +179,22 @@ public class Shortcut : View, IOrientation, IDesignable private void ForceCalculateNaturalWidth () { // Get the natural size of each subview - CommandView.SetRelativeLayout (Application.Screen.Size); - HelpView.SetRelativeLayout (Application.Screen.Size); - KeyView.SetRelativeLayout (Application.Screen.Size); + Size screenSize = App?.Screen.Size ?? new (2048, 2048); + CommandView.SetRelativeLayout (screenSize); + HelpView.SetRelativeLayout (screenSize); + KeyView.SetRelativeLayout (screenSize); _minimumNaturalWidth = PosAlign.CalculateMinDimension (0, SubViews, Dimension.Width); // Reset our relative layout - SetRelativeLayout (SuperView?.GetContentSize () ?? Application.Screen.Size); + SetRelativeLayout (SuperView?.GetContentSize () ?? screenSize); } // TODO: Enable setting of the margin thickness - private Thickness GetMarginThickness () - { - return new (1, 0, 1, 0); - } + private Thickness GetMarginThickness () => new (1, 0, 1, 0); // When layout starts, we need to adjust the layout of the HelpView and KeyView - /// + /// protected override void OnSubViewLayout (LayoutEventArgs e) { base.OnSubViewLayout (e); @@ -229,27 +238,30 @@ public class Shortcut : View, IOrientation, IDesignable } } - #region Accept/Select/HotKey Command Handling private void AddCommands () { // Accept (Enter key) - AddCommand (Command.Accept, DispatchCommand); + // Hotkey - AddCommand (Command.HotKey, DispatchCommand); + // Select (Space key or click) - AddCommand (Command.Select, DispatchCommand); } /// - /// Dispatches the Command in the (Raises Selected, then Accepting, then invoke the Action, if any). + /// Dispatches the Command in the (Raises Selected, then Accepting, then invoke the + /// Action, if any). /// Called when Command.Select, Accept, or HotKey has been invoked on this Shortcut. /// /// /// /// if no event was raised; input processing should continue. - /// if the event was raised and was not handled (or cancelled); input processing should continue. + /// if the event was raised and was not handled (or cancelled); input processing should + /// continue. /// if the event was raised and handled (or cancelled); input processing should stop. /// internal virtual bool? DispatchCommand (ICommandContext? commandContext) @@ -290,6 +302,7 @@ public class Shortcut : View, IOrientation, IDesignable { commandContext.Source = this; } + Logging.Debug ($"{Title} ({commandContext?.Source?.Title}) - Calling RaiseAccepting..."); cancel = RaiseAccepting (commandContext) is true; @@ -378,7 +391,7 @@ public class Shortcut : View, IOrientation, IDesignable /// /// /// This example illustrates how to add a to a that toggles the - /// property. + /// property. /// /// /// var force16ColorsShortcut = new Shortcut @@ -455,8 +468,8 @@ public class Shortcut : View, IOrientation, IDesignable void CommandViewOnSelecting (object? sender, CommandEventArgs e) { - if ((e.Context is CommandContext keyCommandContext && keyCommandContext.Binding.Data != this) || - e.Context is CommandContext) + if ((e.Context is CommandContext keyCommandContext && keyCommandContext.Binding.Data != this) + || e.Context is CommandContext) { // Forward command to ourselves InvokeCommand (Command.Select, new ([Command.Select], null, this)); @@ -472,6 +485,7 @@ public class Shortcut : View, IOrientation, IDesignable if (CommandView.Margin is { }) { CommandView.Margin!.Thickness = GetMarginThickness (); + // strip off ViewportSettings.TransparentMouse CommandView.Margin!.ViewportSettings &= ~ViewportSettingsFlags.TransparentMouse; } @@ -481,6 +495,7 @@ public class Shortcut : View, IOrientation, IDesignable CommandView.VerticalTextAlignment = Alignment.Center; CommandView.TextAlignment = Alignment.Start; CommandView.TextFormatter.WordWrap = false; + //CommandView.HighlightStates = HighlightStates.None; CommandView.GettingAttributeForRole += SubViewOnGettingAttributeForRole; } @@ -495,6 +510,7 @@ public class Shortcut : View, IOrientation, IDesignable e.Handled = true; e.Result = GetAttributeForRole (VisualRole.Focus); } + break; case VisualRole.HotNormal: @@ -503,17 +519,18 @@ public class Shortcut : View, IOrientation, IDesignable e.Handled = true; e.Result = GetAttributeForRole (VisualRole.HotFocus); } + break; } } - private void Shortcut_TitleChanged (object? sender, EventArgs e) { // If the Title changes, update the CommandView text. // This is a helper to make it easier to set the CommandView text. // CommandView is public and replaceable, but this is a convenience. _commandView.Text = Title; + //_commandView.Title = Title; } @@ -522,7 +539,7 @@ public class Shortcut : View, IOrientation, IDesignable #region Help // The maximum width of the HelpView. Calculated in OnLayoutStarted and used in HelpView.Width (Dim.Auto/Func). - private int _maxHelpWidth = 0; + private int _maxHelpWidth; /// /// The subview that displays the help text for the command. Internal for unit testing. @@ -534,20 +551,21 @@ public class Shortcut : View, IOrientation, IDesignable if (HelpView.Margin is { }) { HelpView.Margin!.Thickness = GetMarginThickness (); + // strip off ViewportSettings.TransparentMouse HelpView.Margin!.ViewportSettings &= ~ViewportSettingsFlags.TransparentMouse; } HelpView.X = Pos.Align (Alignment.End, AlignmentModes); _maxHelpWidth = HelpView.Text.GetColumns (); - HelpView.Width = Dim.Auto (DimAutoStyle.Text, maximumContentDim: Dim.Func ((_ => _maxHelpWidth))); + HelpView.Width = Dim.Auto (DimAutoStyle.Text, maximumContentDim: Dim.Func (_ => _maxHelpWidth)); HelpView.Height = Dim.Fill (); HelpView.Visible = true; HelpView.VerticalTextAlignment = Alignment.Center; HelpView.TextAlignment = Alignment.Start; HelpView.TextFormatter.WordWrap = false; - HelpView.HighlightStates = ViewBase.MouseState.None; + HelpView.HighlightStates = MouseState.None; HelpView.GettingAttributeForRole += SubViewOnGettingAttributeForRole; } @@ -605,16 +623,19 @@ public class Shortcut : View, IOrientation, IDesignable } } - private bool _bindKeyToApplication = false; + private bool _bindKeyToApplication; /// - /// Gets or sets whether is bound to via or . + /// Gets or sets whether is bound to via or + /// . /// public bool BindKeyToApplication { get => _bindKeyToApplication; set { + App ??= SuperView?.App; + if (value == _bindKeyToApplication) { return; @@ -622,7 +643,7 @@ public class Shortcut : View, IOrientation, IDesignable if (_bindKeyToApplication) { - Application.KeyBindings.Remove (Key); + App?.Keyboard.KeyBindings.Remove (Key); } else { @@ -661,18 +682,18 @@ public class Shortcut : View, IOrientation, IDesignable } } - private void SetKeyViewDefaultLayout () { if (KeyView.Margin is { }) { KeyView.Margin!.Thickness = GetMarginThickness (); + // strip off ViewportSettings.TransparentMouse KeyView.Margin!.ViewportSettings &= ~ViewportSettingsFlags.TransparentMouse; } KeyView.X = Pos.Align (Alignment.End, AlignmentModes); - KeyView.Width = Dim.Auto (DimAutoStyle.Text, minimumContentDim: Dim.Func (_ => MinimumKeyTextSize)); + KeyView.Width = Dim.Auto (DimAutoStyle.Text, Dim.Func (_ => MinimumKeyTextSize)); KeyView.Height = Dim.Fill (); KeyView.Visible = true; @@ -681,21 +702,23 @@ public class Shortcut : View, IOrientation, IDesignable KeyView.TextAlignment = Alignment.End; KeyView.VerticalTextAlignment = Alignment.Center; KeyView.KeyBindings.Clear (); - KeyView.HighlightStates = ViewBase.MouseState.None; + KeyView.HighlightStates = MouseState.None; KeyView.GettingAttributeForRole += (sender, args) => { if (args.Role == VisualRole.Normal) { - args.Result = SuperView?.GetAttributeForRole (HasFocus ? VisualRole.HotFocus : VisualRole.HotNormal) ?? Attribute.Default; + args.Result = SuperView?.GetAttributeForRole (HasFocus ? VisualRole.HotFocus : VisualRole.HotNormal) + ?? Attribute.Default; args.Handled = true; } }; + KeyView.ClearingViewport += (sender, args) => - { - // Do not clear; otherwise spaces will be printed with underlines - args.Cancel = true; - }; + { + // Do not clear; otherwise spaces will be printed with underlines + args.Cancel = true; + }; } private void UpdateKeyBindings (Key oldKey) @@ -709,11 +732,11 @@ public class Shortcut : View, IOrientation, IDesignable { if (oldKey != Key.Empty) { - Application.KeyBindings.Remove (oldKey); + App?.Keyboard.KeyBindings.Remove (oldKey); } - Application.KeyBindings.Remove (Key); - Application.KeyBindings.Add (Key, this, Command.HotKey); + App?.Keyboard.KeyBindings.Remove (Key); + App?.Keyboard.KeyBindings.Add (Key, this, Command.HotKey); } else { @@ -746,7 +769,7 @@ public class Shortcut : View, IOrientation, IDesignable } } - /// + /// protected override bool OnGettingAttributeForRole (in VisualRole role, ref Attribute currentAttribute) { if (!HasFocus) @@ -760,6 +783,7 @@ public class Shortcut : View, IOrientation, IDesignable return true; } + if (role == VisualRole.HotNormal) { currentAttribute = GetAttributeForRole (VisualRole.HotFocus); @@ -807,4 +831,4 @@ public class Shortcut : View, IOrientation, IDesignable base.Dispose (disposing); } -} \ No newline at end of file +} diff --git a/Terminal.Gui/Views/Slider/Slider.cs b/Terminal.Gui/Views/Slider/Slider.cs index ff0308a56..3f379790a 100644 --- a/Terminal.Gui/Views/Slider/Slider.cs +++ b/Terminal.Gui/Views/Slider/Slider.cs @@ -1,3 +1,4 @@ +#nullable disable using System.Transactions; namespace Terminal.Gui.Views; @@ -1311,7 +1312,7 @@ public class Slider : View, IOrientation { _dragPosition = mouseEvent.Position; _moveRenderPosition = ClampMovePosition ((Point)_dragPosition); - Application.Mouse.GrabMouse (this); + App?.Mouse.GrabMouse (this); } SetNeedsDraw (); @@ -1357,7 +1358,7 @@ public class Slider : View, IOrientation || mouseEvent.Flags.HasFlag (MouseFlags.Button1Clicked)) { // End Drag - Application.Mouse.UngrabMouse (); + App?.Mouse.UngrabMouse (); _dragPosition = null; _moveRenderPosition = null; diff --git a/Terminal.Gui/Views/Slider/SliderAttributes.cs b/Terminal.Gui/Views/Slider/SliderAttributes.cs index db7f199dc..c57c8ab5a 100644 --- a/Terminal.Gui/Views/Slider/SliderAttributes.cs +++ b/Terminal.Gui/Views/Slider/SliderAttributes.cs @@ -11,4 +11,4 @@ public class SliderAttributes /// Attribute for when the respective Option is Set. public Attribute? SetAttribute { get; set; } -} \ No newline at end of file +} diff --git a/Terminal.Gui/Views/Slider/SliderConfiguration.cs b/Terminal.Gui/Views/Slider/SliderConfiguration.cs index 2e75ada0c..1b9354a48 100644 --- a/Terminal.Gui/Views/Slider/SliderConfiguration.cs +++ b/Terminal.Gui/Views/Slider/SliderConfiguration.cs @@ -1,4 +1,4 @@ - + namespace Terminal.Gui.Views; /// All configuration are grouped in this class. @@ -17,4 +17,4 @@ internal class SliderConfiguration internal int _startSpacing; internal SliderType _type = SliderType.Single; internal bool _useMinimumSize; -} \ No newline at end of file +} diff --git a/Terminal.Gui/Views/Slider/SliderEventArgs.cs b/Terminal.Gui/Views/Slider/SliderEventArgs.cs index b4f93ca12..0b55b4012 100644 --- a/Terminal.Gui/Views/Slider/SliderEventArgs.cs +++ b/Terminal.Gui/Views/Slider/SliderEventArgs.cs @@ -1,3 +1,4 @@ +#nullable disable namespace Terminal.Gui.Views; /// for events. @@ -21,4 +22,4 @@ public class SliderEventArgs : EventArgs /// Gets/sets whether the option is set or not. public Dictionary> Options { get; set; } -} \ No newline at end of file +} diff --git a/Terminal.Gui/Views/Slider/SliderOption.cs b/Terminal.Gui/Views/Slider/SliderOption.cs index 95a929d6a..d5207c301 100644 --- a/Terminal.Gui/Views/Slider/SliderOption.cs +++ b/Terminal.Gui/Views/Slider/SliderOption.cs @@ -1,3 +1,4 @@ +#nullable disable namespace Terminal.Gui.Views; /// Represents an option in a . @@ -47,4 +48,4 @@ public class SliderOption /// To Raise the event from the Slider. internal void OnUnSet () { UnSet?.Invoke (this, new (false)); } -} \ No newline at end of file +} diff --git a/Terminal.Gui/Views/Slider/SliderOptionEventArgs.cs b/Terminal.Gui/Views/Slider/SliderOptionEventArgs.cs index 8014c2a77..5176f9601 100644 --- a/Terminal.Gui/Views/Slider/SliderOptionEventArgs.cs +++ b/Terminal.Gui/Views/Slider/SliderOptionEventArgs.cs @@ -1,3 +1,4 @@ +#nullable disable namespace Terminal.Gui.Views; /// for events. @@ -9,4 +10,4 @@ public class SliderOptionEventArgs : EventArgs /// Gets whether the option is set or not. public bool IsSet { get; } -} \ No newline at end of file +} diff --git a/Terminal.Gui/Views/Slider/SliderStyle.cs b/Terminal.Gui/Views/Slider/SliderStyle.cs index 38c6570c3..aa5557116 100644 --- a/Terminal.Gui/Views/Slider/SliderStyle.cs +++ b/Terminal.Gui/Views/Slider/SliderStyle.cs @@ -33,4 +33,4 @@ public class SliderStyle /// The glyph and the attribute used for the start of ranges on the slider. public Cell StartRangeChar { get; set; } -} \ No newline at end of file +} diff --git a/Terminal.Gui/Views/Slider/SliderType.cs b/Terminal.Gui/Views/Slider/SliderType.cs index 7ad287a97..7715aa3c3 100644 --- a/Terminal.Gui/Views/Slider/SliderType.cs +++ b/Terminal.Gui/Views/Slider/SliderType.cs @@ -37,4 +37,4 @@ public enum SliderType /// /// Range -} \ No newline at end of file +} diff --git a/Terminal.Gui/Views/SpinnerView/SpinnerView.cs b/Terminal.Gui/Views/SpinnerView/SpinnerView.cs index 00f723fe6..be1337591 100644 --- a/Terminal.Gui/Views/SpinnerView/SpinnerView.cs +++ b/Terminal.Gui/Views/SpinnerView/SpinnerView.cs @@ -1,4 +1,4 @@ -#nullable enable + //------------------------------------------------------------------------------ // Windows Terminal supports Unicode and Emoji characters, but by default @@ -114,7 +114,7 @@ public class SpinnerView : View, IDesignable /// Advances the animation frame and notifies main loop that repainting needs to happen. Repeated calls are /// ignored based on . /// - /// Ensure this method is called on the main UI thread e.g. via + /// Ensure this method is called on the main UI thread e.g. via public void AdvanceAnimation (bool setNeedsDraw = true) { if (DateTime.Now - _lastRender > TimeSpan.FromMilliseconds (SpinDelay)) @@ -202,16 +202,16 @@ public class SpinnerView : View, IDesignable private void AddAutoSpinTimeout () { // Only add timeout if we are initialized and not already spinning - if (_timeout is { } || !Application.Initialized) + if (App is { } && (_timeout is { } || !App.Initialized)) { return; } - _timeout = Application.AddTimeout ( + _timeout = App?.AddTimeout ( TimeSpan.FromMilliseconds (SpinDelay), () => { - Application.Invoke (() => AdvanceAnimation ()); + App.Invoke ((_) => AdvanceAnimation ()); return true; } @@ -266,7 +266,7 @@ public class SpinnerView : View, IDesignable { if (_timeout is { }) { - Application.RemoveTimeout (_timeout); + App?.RemoveTimeout (_timeout); _timeout = null; } } diff --git a/Terminal.Gui/Views/StatusBar.cs b/Terminal.Gui/Views/StatusBar.cs index 3d58a7afc..cd97abd06 100644 --- a/Terminal.Gui/Views/StatusBar.cs +++ b/Terminal.Gui/Views/StatusBar.cs @@ -1,6 +1,3 @@ -#nullable enable - - namespace Terminal.Gui.Views; /// @@ -137,12 +134,14 @@ public class StatusBar : Bar, IDesignable button1.Accepting += OnButtonClicked; Add (button1); - shortcut.Accepting += (s, e) => - { - button1.Visible = !button1.Visible; - button1.Enabled = button1.Visible; - e.Handled = false; - }; +#pragma warning disable TGUI001 + shortcut.Accepting += (_, e) => + { + button1.Visible = !button1.Visible; + button1.Enabled = button1.Visible; + e.Handled = false; + }; +#pragma warning restore TGUI001 Add (new Label { @@ -155,7 +154,7 @@ public class StatusBar : Bar, IDesignable { Text = "Or me!", }; - button2.Accepting += (s, e) => Application.RequestStop (); + button2.Accepting += (s, e) => App?.RequestStop (); Add (button2); diff --git a/Terminal.Gui/Views/TabView/Tab.cs b/Terminal.Gui/Views/TabView/Tab.cs index f0025b983..30562810d 100644 --- a/Terminal.Gui/Views/TabView/Tab.cs +++ b/Terminal.Gui/Views/TabView/Tab.cs @@ -1,4 +1,4 @@ -#nullable enable + namespace Terminal.Gui.Views; diff --git a/Terminal.Gui/Views/TabView/TabChangedEventArgs.cs b/Terminal.Gui/Views/TabView/TabChangedEventArgs.cs index acaa90fd6..4a769c688 100644 --- a/Terminal.Gui/Views/TabView/TabChangedEventArgs.cs +++ b/Terminal.Gui/Views/TabView/TabChangedEventArgs.cs @@ -1,3 +1,4 @@ +#nullable disable namespace Terminal.Gui.Views; /// Describes a change in diff --git a/Terminal.Gui/Views/TabView/TabMouseEventArgs.cs b/Terminal.Gui/Views/TabView/TabMouseEventArgs.cs index f52792549..94c55803a 100644 --- a/Terminal.Gui/Views/TabView/TabMouseEventArgs.cs +++ b/Terminal.Gui/Views/TabView/TabMouseEventArgs.cs @@ -1,4 +1,4 @@ -#nullable enable + using System.ComponentModel; namespace Terminal.Gui.Views; diff --git a/Terminal.Gui/Views/TabView/TabRow.cs b/Terminal.Gui/Views/TabView/TabRow.cs index f85db683a..5534ac5be 100644 --- a/Terminal.Gui/Views/TabView/TabRow.cs +++ b/Terminal.Gui/Views/TabView/TabRow.cs @@ -1,4 +1,3 @@ -#nullable enable namespace Terminal.Gui.Views; diff --git a/Terminal.Gui/Views/TabView/TabStyle.cs b/Terminal.Gui/Views/TabView/TabStyle.cs index 07ac12884..caf059ba0 100644 --- a/Terminal.Gui/Views/TabView/TabStyle.cs +++ b/Terminal.Gui/Views/TabView/TabStyle.cs @@ -1,3 +1,4 @@ +#nullable disable namespace Terminal.Gui.Views; /// Describes render stylistic selections of a diff --git a/Terminal.Gui/Views/TabView/TabView.cs b/Terminal.Gui/Views/TabView/TabView.cs index 6c222d9f3..f401c500b 100644 --- a/Terminal.Gui/Views/TabView/TabView.cs +++ b/Terminal.Gui/Views/TabView/TabView.cs @@ -1,4 +1,3 @@ -#nullable enable namespace Terminal.Gui.Views; diff --git a/Terminal.Gui/Views/TableView/CellActivatedEventArgs.cs b/Terminal.Gui/Views/TableView/CellActivatedEventArgs.cs index d5dc4eb3e..cb27a1b43 100644 --- a/Terminal.Gui/Views/TableView/CellActivatedEventArgs.cs +++ b/Terminal.Gui/Views/TableView/CellActivatedEventArgs.cs @@ -1,3 +1,4 @@ +#nullable disable namespace Terminal.Gui.Views; // TOOD: SHould support Handled diff --git a/Terminal.Gui/Views/TableView/CellColorGetterArgs.cs b/Terminal.Gui/Views/TableView/CellColorGetterArgs.cs index 2dfcc069e..48a0b492e 100644 --- a/Terminal.Gui/Views/TableView/CellColorGetterArgs.cs +++ b/Terminal.Gui/Views/TableView/CellColorGetterArgs.cs @@ -1,3 +1,4 @@ +#nullable disable  namespace Terminal.Gui.Views; diff --git a/Terminal.Gui/Views/TableView/CellToggledEventArgs.cs b/Terminal.Gui/Views/TableView/CellToggledEventArgs.cs index 281dbb6f9..f0a88aaae 100644 --- a/Terminal.Gui/Views/TableView/CellToggledEventArgs.cs +++ b/Terminal.Gui/Views/TableView/CellToggledEventArgs.cs @@ -1,3 +1,4 @@ +#nullable disable namespace Terminal.Gui.Views; /// Event args for the event. diff --git a/Terminal.Gui/Views/TableView/CheckBoxTableSourceWrapper.cs b/Terminal.Gui/Views/TableView/CheckBoxTableSourceWrapper.cs index c058b2557..5d0b41ed9 100644 --- a/Terminal.Gui/Views/TableView/CheckBoxTableSourceWrapper.cs +++ b/Terminal.Gui/Views/TableView/CheckBoxTableSourceWrapper.cs @@ -1,3 +1,4 @@ +#nullable disable  namespace Terminal.Gui.Views; diff --git a/Terminal.Gui/Views/TableView/CheckBoxTableSourceWrapperByIndex.cs b/Terminal.Gui/Views/TableView/CheckBoxTableSourceWrapperByIndex.cs index b82bebb13..7e1c4ab58 100644 --- a/Terminal.Gui/Views/TableView/CheckBoxTableSourceWrapperByIndex.cs +++ b/Terminal.Gui/Views/TableView/CheckBoxTableSourceWrapperByIndex.cs @@ -1,3 +1,4 @@ +#nullable disable namespace Terminal.Gui.Views; /// Implementation of which records toggled rows by their row number. diff --git a/Terminal.Gui/Views/TableView/CheckBoxTableSourceWrapperByObject.cs b/Terminal.Gui/Views/TableView/CheckBoxTableSourceWrapperByObject.cs index faa746ced..87028346f 100644 --- a/Terminal.Gui/Views/TableView/CheckBoxTableSourceWrapperByObject.cs +++ b/Terminal.Gui/Views/TableView/CheckBoxTableSourceWrapperByObject.cs @@ -1,3 +1,4 @@ +#nullable disable namespace Terminal.Gui.Views; /// diff --git a/Terminal.Gui/Views/TableView/ColumnStyle.cs b/Terminal.Gui/Views/TableView/ColumnStyle.cs index e9afecaa7..8a1ca6526 100644 --- a/Terminal.Gui/Views/TableView/ColumnStyle.cs +++ b/Terminal.Gui/Views/TableView/ColumnStyle.cs @@ -1,3 +1,4 @@ +#nullable disable  namespace Terminal.Gui.Views; diff --git a/Terminal.Gui/Views/TableView/DataTableSource.cs b/Terminal.Gui/Views/TableView/DataTableSource.cs index 551b38b05..e8ac64b2e 100644 --- a/Terminal.Gui/Views/TableView/DataTableSource.cs +++ b/Terminal.Gui/Views/TableView/DataTableSource.cs @@ -1,3 +1,4 @@ +#nullable disable using System.Data; namespace Terminal.Gui.Views; diff --git a/Terminal.Gui/Views/TableView/EnumerableTableSource.cs b/Terminal.Gui/Views/TableView/EnumerableTableSource.cs index a771ca6fd..3499a63e1 100644 --- a/Terminal.Gui/Views/TableView/EnumerableTableSource.cs +++ b/Terminal.Gui/Views/TableView/EnumerableTableSource.cs @@ -1,3 +1,4 @@ +#nullable disable namespace Terminal.Gui.Views; /// implementation that wraps arbitrary data. diff --git a/Terminal.Gui/Views/TableView/IEnumerableTableSource.cs b/Terminal.Gui/Views/TableView/IEnumerableTableSource.cs index c21700836..7827b9c77 100644 --- a/Terminal.Gui/Views/TableView/IEnumerableTableSource.cs +++ b/Terminal.Gui/Views/TableView/IEnumerableTableSource.cs @@ -1,3 +1,4 @@ +#nullable disable namespace Terminal.Gui.Views; /// diff --git a/Terminal.Gui/Views/TableView/ITableSource.cs b/Terminal.Gui/Views/TableView/ITableSource.cs index 4848642da..c9728efc8 100644 --- a/Terminal.Gui/Views/TableView/ITableSource.cs +++ b/Terminal.Gui/Views/TableView/ITableSource.cs @@ -1,3 +1,4 @@ +#nullable disable namespace Terminal.Gui.Views; /// Tabular matrix of data to be displayed in a . diff --git a/Terminal.Gui/Views/TableView/ListColumnStyle.cs b/Terminal.Gui/Views/TableView/ListColumnStyle.cs index cce3376b0..e805e6338 100644 --- a/Terminal.Gui/Views/TableView/ListColumnStyle.cs +++ b/Terminal.Gui/Views/TableView/ListColumnStyle.cs @@ -1,4 +1,4 @@ - + namespace Terminal.Gui.Views; /// Defines rendering options that affect how the view is displayed. diff --git a/Terminal.Gui/Views/TableView/ListTableSource.cs b/Terminal.Gui/Views/TableView/ListTableSource.cs index bc07d404f..7f535c300 100644 --- a/Terminal.Gui/Views/TableView/ListTableSource.cs +++ b/Terminal.Gui/Views/TableView/ListTableSource.cs @@ -1,3 +1,4 @@ +#nullable disable using System.Collections; using System.Data; diff --git a/Terminal.Gui/Views/TableView/RowColorGetterArgs.cs b/Terminal.Gui/Views/TableView/RowColorGetterArgs.cs index 6335a484d..c17e0c958 100644 --- a/Terminal.Gui/Views/TableView/RowColorGetterArgs.cs +++ b/Terminal.Gui/Views/TableView/RowColorGetterArgs.cs @@ -1,3 +1,4 @@ +#nullable disable namespace Terminal.Gui.Views; /// diff --git a/Terminal.Gui/Views/TableView/SelectedCellChangedEventArgs.cs b/Terminal.Gui/Views/TableView/SelectedCellChangedEventArgs.cs index 4aad9ccae..733f32b3d 100644 --- a/Terminal.Gui/Views/TableView/SelectedCellChangedEventArgs.cs +++ b/Terminal.Gui/Views/TableView/SelectedCellChangedEventArgs.cs @@ -1,3 +1,4 @@ +#nullable disable namespace Terminal.Gui.Views; /// Defines the event arguments for diff --git a/Terminal.Gui/Views/TableView/TableSelection.cs b/Terminal.Gui/Views/TableView/TableSelection.cs index f6217ef0b..2fe21f9cd 100644 --- a/Terminal.Gui/Views/TableView/TableSelection.cs +++ b/Terminal.Gui/Views/TableView/TableSelection.cs @@ -1,3 +1,4 @@ +#nullable disable namespace Terminal.Gui.Views; /// Describes a selected region of the table diff --git a/Terminal.Gui/Views/TableView/TableStyle.cs b/Terminal.Gui/Views/TableView/TableStyle.cs index e52cddf1f..8640fa694 100644 --- a/Terminal.Gui/Views/TableView/TableStyle.cs +++ b/Terminal.Gui/Views/TableView/TableStyle.cs @@ -1,3 +1,4 @@ +#nullable disable namespace Terminal.Gui.Views; /// diff --git a/Terminal.Gui/Views/TableView/TableView.cs b/Terminal.Gui/Views/TableView/TableView.cs index 272e7495d..f09532ed2 100644 --- a/Terminal.Gui/Views/TableView/TableView.cs +++ b/Terminal.Gui/Views/TableView/TableView.cs @@ -1,3 +1,4 @@ +#nullable disable using System.Data; using System.Globalization; diff --git a/Terminal.Gui/Views/TableView/TreeTableSource.cs b/Terminal.Gui/Views/TableView/TreeTableSource.cs index 6cbb5b0a7..8c6925399 100644 --- a/Terminal.Gui/Views/TableView/TreeTableSource.cs +++ b/Terminal.Gui/Views/TableView/TreeTableSource.cs @@ -1,4 +1,5 @@ +#nullable disable namespace Terminal.Gui.Views; /// An with expandable rows. diff --git a/Terminal.Gui/Views/TextInput/ContentsChangedEventArgs.cs b/Terminal.Gui/Views/TextInput/ContentsChangedEventArgs.cs index 23e1a5dab..58eac9160 100644 --- a/Terminal.Gui/Views/TextInput/ContentsChangedEventArgs.cs +++ b/Terminal.Gui/Views/TextInput/ContentsChangedEventArgs.cs @@ -1,3 +1,4 @@ +#nullable disable // TextView.cs: multi-line text editing namespace Terminal.Gui.Views; diff --git a/Terminal.Gui/Views/TextInput/DateField.cs b/Terminal.Gui/Views/TextInput/DateField.cs index 2da26f9a0..74c48f5f3 100644 --- a/Terminal.Gui/Views/TextInput/DateField.cs +++ b/Terminal.Gui/Views/TextInput/DateField.cs @@ -1,4 +1,4 @@ -#nullable enable + // // DateField.cs: text entry for date diff --git a/Terminal.Gui/Views/TextInput/HistoryText.cs b/Terminal.Gui/Views/TextInput/HistoryText.cs index 62fd65c8b..7b04f1676 100644 --- a/Terminal.Gui/Views/TextInput/HistoryText.cs +++ b/Terminal.Gui/Views/TextInput/HistoryText.cs @@ -1,4 +1,3 @@ -#nullable enable namespace Terminal.Gui.Views; diff --git a/Terminal.Gui/Views/TextInput/HistoryTextItemEventArgs.cs b/Terminal.Gui/Views/TextInput/HistoryTextItemEventArgs.cs index 875c039a3..bf67d14c2 100644 --- a/Terminal.Gui/Views/TextInput/HistoryTextItemEventArgs.cs +++ b/Terminal.Gui/Views/TextInput/HistoryTextItemEventArgs.cs @@ -1,4 +1,4 @@ -#nullable enable + namespace Terminal.Gui.Views; diff --git a/Terminal.Gui/Views/TextInput/ITextValidateProvider.cs b/Terminal.Gui/Views/TextInput/ITextValidateProvider.cs index e25306cd3..03c614da9 100644 --- a/Terminal.Gui/Views/TextInput/ITextValidateProvider.cs +++ b/Terminal.Gui/Views/TextInput/ITextValidateProvider.cs @@ -1,4 +1,4 @@ -#nullable enable + namespace Terminal.Gui.Views; diff --git a/Terminal.Gui/Views/TextInput/NetMaskedTextProvider.cs b/Terminal.Gui/Views/TextInput/NetMaskedTextProvider.cs index f082807c6..0ac0659cd 100644 --- a/Terminal.Gui/Views/TextInput/NetMaskedTextProvider.cs +++ b/Terminal.Gui/Views/TextInput/NetMaskedTextProvider.cs @@ -1,5 +1,4 @@ -#nullable enable -using System.ComponentModel; +using System.ComponentModel; namespace Terminal.Gui.Views; diff --git a/Terminal.Gui/Views/TextInput/TextEditingLineStatus.cs b/Terminal.Gui/Views/TextInput/TextEditingLineStatus.cs index e71f6de1d..0259b15ee 100644 --- a/Terminal.Gui/Views/TextInput/TextEditingLineStatus.cs +++ b/Terminal.Gui/Views/TextInput/TextEditingLineStatus.cs @@ -9,6 +9,7 @@ /// line. /// // ReSharper disable once CheckNamespace +#nullable disable public enum TextEditingLineStatus { /// diff --git a/Terminal.Gui/Views/TextInput/TextField.cs b/Terminal.Gui/Views/TextInput/TextField.cs index bc2390fb6..37a92062b 100644 --- a/Terminal.Gui/Views/TextInput/TextField.cs +++ b/Terminal.Gui/Views/TextInput/TextField.cs @@ -1,3 +1,4 @@ +#nullable disable using System.Globalization; namespace Terminal.Gui.Views; @@ -418,9 +419,6 @@ public class TextField : View, IDesignable KeyBindings.Remove (Key.Space); _currentCulture = Thread.CurrentThread.CurrentUICulture; - - CreateContextMenu (); - KeyBindings.Add (ContextMenu.Key, Command.Context); } /// @@ -864,16 +862,16 @@ public class TextField : View, IDesignable _isButtonReleased = false; PrepareSelection (x); - if (Application.Mouse.MouseGrabView is null) + if (App?.Mouse.MouseGrabView is null) { - Application.Mouse.GrabMouse (this); + App?.Mouse.GrabMouse (this); } } else if (ev.Flags == MouseFlags.Button1Released) { _isButtonReleased = true; _isButtonPressed = false; - Application.Mouse.UngrabMouse (); + App?.Mouse.UngrabMouse (); } else if (ev.Flags == MouseFlags.Button1DoubleClicked) { @@ -1016,13 +1014,10 @@ public class TextField : View, IDesignable /// protected override void OnHasFocusChanged (bool newHasFocus, View previousFocusedView, View view) { - if (Application.Mouse.MouseGrabView is { } && Application.Mouse.MouseGrabView == this) + if (App?.Mouse.MouseGrabView is { } && App?.Mouse.MouseGrabView == this) { - Application.Mouse.UngrabMouse (); + App?.Mouse.UngrabMouse (); } - - //if (SelectedLength != 0 && !(Application.Mouse.MouseGrabView is MenuBar)) - // ClearAllSelection (); } /// @@ -1254,6 +1249,7 @@ public class TextField : View, IDesignable menu.KeyChanged += ContextMenu_KeyChanged; ContextMenu = menu; + App?.Popover.Register (ContextMenu); } private void ContextMenu_KeyChanged (object sender, KeyChangedEventArgs e) { KeyBindings.Replace (e.OldKey.KeyCode, e.NewKey.KeyCode); } @@ -1730,10 +1726,7 @@ public class TextField : View, IDesignable GetAttributeForRole (VisualRole.Editable).Style | TextStyle.Underline); // Use TitleTextFormatter to render the caption with hotkey support - TitleTextFormatter.Draw ( - ViewportToScreen (new Rectangle (0, 0, Viewport.Width, 1)), - captionAttribute, - hotKeyAttribute); + TitleTextFormatter.Draw (driver: Driver, screen: ViewportToScreen (new Rectangle (0, 0, Viewport.Width, 1)), normalColor: captionAttribute, hotColor: hotKeyAttribute); } private void SetClipboard (IEnumerable text) @@ -1770,11 +1763,6 @@ public class TextField : View, IDesignable if (!Equals (_currentCulture, Thread.CurrentThread.CurrentUICulture)) { _currentCulture = Thread.CurrentThread.CurrentUICulture; - - if (ContextMenu is { }) - { - CreateContextMenu (); - } } if (keyboard) @@ -1817,6 +1805,10 @@ public class TextField : View, IDesignable Autocomplete.HostControl = this; Autocomplete.PopupInsideContainer = false; } + + CreateContextMenu (); + KeyBindings.Add (ContextMenu?.Key, Command.Context); + } private void DisposeContextMenu () diff --git a/Terminal.Gui/Views/TextInput/TextModel.cs b/Terminal.Gui/Views/TextInput/TextModel.cs index 275c8a908..81d07848a 100644 --- a/Terminal.Gui/Views/TextInput/TextModel.cs +++ b/Terminal.Gui/Views/TextInput/TextModel.cs @@ -1,4 +1,3 @@ -#nullable enable namespace Terminal.Gui.Views; diff --git a/Terminal.Gui/Views/TextInput/TextRegexProvider.cs b/Terminal.Gui/Views/TextInput/TextRegexProvider.cs index 753c8c1bc..34ad5e6ff 100644 --- a/Terminal.Gui/Views/TextInput/TextRegexProvider.cs +++ b/Terminal.Gui/Views/TextInput/TextRegexProvider.cs @@ -1,4 +1,4 @@ -#nullable enable + using System.Text.RegularExpressions; namespace Terminal.Gui.Views; diff --git a/Terminal.Gui/Views/TextInput/TextValidateField.cs b/Terminal.Gui/Views/TextInput/TextValidateField.cs index f7e897feb..db4cbbd54 100644 --- a/Terminal.Gui/Views/TextInput/TextValidateField.cs +++ b/Terminal.Gui/Views/TextInput/TextValidateField.cs @@ -1,4 +1,4 @@ -#nullable enable + namespace Terminal.Gui.Views; diff --git a/Terminal.Gui/Views/TextInput/TextView.cs b/Terminal.Gui/Views/TextInput/TextView.cs index f9e2031d9..8b5da5ef3 100644 --- a/Terminal.Gui/Views/TextInput/TextView.cs +++ b/Terminal.Gui/Views/TextInput/TextView.cs @@ -1,6 +1,3 @@ -#nullable enable - -// TextView.cs: multi-line text editing using System.Globalization; using System.Runtime.CompilerServices; @@ -116,7 +113,7 @@ public class TextView : View, IDesignable Used = true; // By default, disable hotkeys (in case someone sets Title) - HotKeySpecifier = new ('\xffff'); + base.HotKeySpecifier = new ('\xffff'); _model.LinesLoaded += Model_LinesLoaded!; _historyText.ChangeText += HistoryText_ChangeText!; @@ -629,9 +626,6 @@ public class TextView : View, IDesignable #endif _currentCulture = Thread.CurrentThread.CurrentUICulture; - - ContextMenu = CreateContextMenu (); - KeyBindings.Add (ContextMenu.Key, Command.Context); } // BUGBUG: AllowsReturn is mis-named. It should be EnterKeyAccepts. @@ -1676,15 +1670,15 @@ public class TextView : View, IDesignable _lastWasKill = false; _columnTrack = CurrentColumn; - if (Application.Mouse.MouseGrabView is null) + if (App?.Mouse.MouseGrabView is null) { - Application.Mouse.GrabMouse (this); + App?.Mouse.GrabMouse (this); } } else if (ev.Flags.HasFlag (MouseFlags.Button1Released)) { _isButtonReleased = true; - Application.Mouse.UngrabMouse (); + App?.Mouse.UngrabMouse (); } else if (ev.Flags.HasFlag (MouseFlags.Button1DoubleClicked)) { @@ -1886,9 +1880,9 @@ public class TextView : View, IDesignable /// protected override void OnHasFocusChanged (bool newHasFocus, View? previousFocusedView, View? view) { - if (Application.Mouse.MouseGrabView is { } && Application.Mouse.MouseGrabView == this) + if (App?.Mouse.MouseGrabView is { } && App?.Mouse.MouseGrabView == this) { - Application.Mouse.UngrabMouse (); + App?.Mouse.UngrabMouse (); } } @@ -2027,12 +2021,12 @@ public class TextView : View, IDesignable { ProcessAutocomplete (); - if (!CanFocus || !Enabled || Application.Driver is null) + if (!CanFocus || !Enabled || Driver is null) { return null; } - if (Application.Mouse.MouseGrabView == this && IsSelecting) + if (App?.Mouse.MouseGrabView == this && IsSelecting) { // BUGBUG: customized rect aren't supported now because the Redraw isn't using the Intersect method. //var minRow = Math.Min (Math.Max (Math.Min (selectionStartRow, currentRow) - topRow, 0), Viewport.Height); @@ -4578,6 +4572,7 @@ public class TextView : View, IDesignable { mousePosition = ViewportToScreen (new Point (CursorPosition.X, CursorPosition.Y)); } + ContextMenu?.MakeVisible (mousePosition); } @@ -4653,6 +4648,11 @@ public class TextView : View, IDesignable Autocomplete.HostControl = this; } + + ContextMenu = CreateContextMenu (); + App?.Popover?.Register (ContextMenu); + KeyBindings.Add (ContextMenu.Key, Command.Context); + OnContentsChanged (); } @@ -4790,15 +4790,15 @@ public class TextView : View, IDesignable public class TextViewAutocomplete : PopupAutocomplete { /// - protected override void DeleteTextBackwards () { ((TextView)HostControl).DeleteCharLeft (); } + protected override void DeleteTextBackwards () { ((TextView)HostControl!).DeleteCharLeft (); } /// - protected override void InsertText (string accepted) { ((TextView)HostControl).InsertText (accepted); } + protected override void InsertText (string accepted) { ((TextView)HostControl!).InsertText (accepted); } /// protected override void SetCursorPosition (int column) { - ((TextView)HostControl).CursorPosition = + ((TextView)HostControl!).CursorPosition = new (column, ((TextView)HostControl).CurrentRow); } } diff --git a/Terminal.Gui/Views/TextInput/TimeField.cs b/Terminal.Gui/Views/TextInput/TimeField.cs index 59a906f63..e61e48d07 100644 --- a/Terminal.Gui/Views/TextInput/TimeField.cs +++ b/Terminal.Gui/Views/TextInput/TimeField.cs @@ -5,6 +5,7 @@ // // Licensed under the MIT license +#nullable disable using System.Globalization; namespace Terminal.Gui.Views; diff --git a/Terminal.Gui/Views/TextInput/WordWrapManager.cs b/Terminal.Gui/Views/TextInput/WordWrapManager.cs index ced8c2a7c..705e07527 100644 --- a/Terminal.Gui/Views/TextInput/WordWrapManager.cs +++ b/Terminal.Gui/Views/TextInput/WordWrapManager.cs @@ -1,4 +1,3 @@ -#nullable enable namespace Terminal.Gui.Views; diff --git a/Terminal.Gui/Views/Toplevel.cs b/Terminal.Gui/Views/Toplevel.cs index 926eb6aef..9f3854f7e 100644 --- a/Terminal.Gui/Views/Toplevel.cs +++ b/Terminal.Gui/Views/Toplevel.cs @@ -1,5 +1,3 @@ -#nullable enable - namespace Terminal.Gui.Views; /// @@ -9,15 +7,15 @@ namespace Terminal.Gui.Views; /// /// /// Toplevel views can run as modal (popup) views, started by calling -/// . They return control to the caller when -/// has been called (which sets the +/// . They return control to the caller when +/// has been called (which sets the /// property to false). /// /// -/// A Toplevel is created when an application initializes Terminal.Gui by calling . -/// The application Toplevel can be accessed via . Additional Toplevels can be created +/// A Toplevel is created when an application initializes Terminal.Gui by calling . +/// The application Toplevel can be accessed via . Additional Toplevels can be created /// and run (e.g. s). To run a Toplevel, create the and call -/// . +/// . /// /// public partial class Toplevel : View @@ -84,12 +82,12 @@ public partial class Toplevel : View // TODO: IRunnable: Re-implement as a property on IRunnable /// Gets or sets whether the main loop for this is running or not. - /// Setting this property directly is discouraged. Use instead. + /// Setting this property directly is discouraged. Use instead. public bool Running { get; set; } // TODO: IRunnable: Re-implement in IRunnable /// - /// if was already loaded by the + /// if was already loaded by the /// , otherwise. /// public bool IsLoaded { get; private set; } @@ -102,12 +100,12 @@ public partial class Toplevel : View /// Invoked when the Toplevel ceases to be active. public event EventHandler? Deactivate; - /// Invoked when the Toplevel's is closed by . + /// Invoked when the Toplevel's is closed by . public event EventHandler? Closed; /// /// Invoked when the Toplevel's is being closed by - /// . + /// . /// public event EventHandler? Closing; @@ -118,7 +116,7 @@ public partial class Toplevel : View public event EventHandler? Loaded; /// - /// Called from before the redraws for the first + /// Called from before the redraws for the first /// time. /// /// @@ -143,23 +141,23 @@ public partial class Toplevel : View /// perform tasks when the has been laid out and focus has been set. changes. /// /// A Ready event handler is a good place to finalize initialization after calling - /// on this . + /// on this . /// /// public event EventHandler? Ready; /// /// Stops and closes this . If this Toplevel is the top-most Toplevel, - /// will be called, causing the application to exit. + /// will be called, causing the application to exit. /// public virtual void RequestStop () { - Application.RequestStop (Application.Top); + App?.RequestStop (App?.Current); } /// /// Invoked when the Toplevel has been unloaded. A Unloaded event handler is a good place - /// to dispose objects after calling . + /// to dispose objects after calling . /// public event EventHandler? Unloaded; @@ -191,7 +189,7 @@ public partial class Toplevel : View Ready?.Invoke (this, EventArgs.Empty); } - /// Called from before the is disposed. + /// Called from before the is disposed. internal virtual void OnUnloaded () { foreach (var view in SubViews.Where (v => v is Toplevel)) @@ -246,7 +244,7 @@ public partial class Toplevel : View } // BUGBUG: The && true is a temp hack - if ((superView != top || top?.SuperView is { } || (top != Application.Top && top!.Modal) || (top == Application.Top && top?.SuperView is null)) + if ((superView != top || top?.SuperView is { } || (top != App?.Current && top!.Modal) || (top == App?.Current && top?.SuperView is null)) && (top!.Frame.X + top.Frame.Width > maxWidth || ny > top.Frame.Y)) { diff --git a/Terminal.Gui/Views/ToplevelEventArgs.cs b/Terminal.Gui/Views/ToplevelEventArgs.cs index 0c8d15fc7..52e02d0c8 100644 --- a/Terminal.Gui/Views/ToplevelEventArgs.cs +++ b/Terminal.Gui/Views/ToplevelEventArgs.cs @@ -1,3 +1,4 @@ +#nullable disable  namespace Terminal.Gui.Views; diff --git a/Terminal.Gui/Views/TreeView/AspectGetterDelegate.cs b/Terminal.Gui/Views/TreeView/AspectGetterDelegate.cs index 4382da300..df04b9047 100644 --- a/Terminal.Gui/Views/TreeView/AspectGetterDelegate.cs +++ b/Terminal.Gui/Views/TreeView/AspectGetterDelegate.cs @@ -1,3 +1,4 @@ +#nullable disable namespace Terminal.Gui.Views; /// Delegates of this type are used to fetch string representations of user's model objects diff --git a/Terminal.Gui/Views/TreeView/Branch.cs b/Terminal.Gui/Views/TreeView/Branch.cs index 811824dbf..9c48a0a0d 100644 --- a/Terminal.Gui/Views/TreeView/Branch.cs +++ b/Terminal.Gui/Views/TreeView/Branch.cs @@ -1,4 +1,4 @@ -#nullable enable + namespace Terminal.Gui.Views; diff --git a/Terminal.Gui/Views/TreeView/DelegateTreeBuilder.cs b/Terminal.Gui/Views/TreeView/DelegateTreeBuilder.cs index 131dff2d6..7bc59d1b0 100644 --- a/Terminal.Gui/Views/TreeView/DelegateTreeBuilder.cs +++ b/Terminal.Gui/Views/TreeView/DelegateTreeBuilder.cs @@ -1,3 +1,4 @@ +#nullable disable namespace Terminal.Gui.Views; /// Implementation of that uses user defined functions diff --git a/Terminal.Gui/Views/TreeView/DrawTreeViewLineEventArgs.cs b/Terminal.Gui/Views/TreeView/DrawTreeViewLineEventArgs.cs index 1ef543eeb..2b27697d0 100644 --- a/Terminal.Gui/Views/TreeView/DrawTreeViewLineEventArgs.cs +++ b/Terminal.Gui/Views/TreeView/DrawTreeViewLineEventArgs.cs @@ -1,3 +1,4 @@ +#nullable disable // This code is based on http://objectlistview.sourceforge.net (GPLv3 tree/list controls // by phillip.piper@gmail.com). Phillip has explicitly granted permission for his design // and code to be used in this library under the MIT license. diff --git a/Terminal.Gui/Views/TreeView/ITreeBuilder.cs b/Terminal.Gui/Views/TreeView/ITreeBuilder.cs index f03129525..c4c4be1dd 100644 --- a/Terminal.Gui/Views/TreeView/ITreeBuilder.cs +++ b/Terminal.Gui/Views/TreeView/ITreeBuilder.cs @@ -1,3 +1,4 @@ +#nullable disable namespace Terminal.Gui.Views; /// diff --git a/Terminal.Gui/Views/TreeView/ITreeViewFilter.cs b/Terminal.Gui/Views/TreeView/ITreeViewFilter.cs index dde9a3a98..3285d6ed1 100644 --- a/Terminal.Gui/Views/TreeView/ITreeViewFilter.cs +++ b/Terminal.Gui/Views/TreeView/ITreeViewFilter.cs @@ -1,3 +1,4 @@ +#nullable disable namespace Terminal.Gui.Views; /// Provides filtering for a . diff --git a/Terminal.Gui/Views/TreeView/ObjectActivatedEventArgs.cs b/Terminal.Gui/Views/TreeView/ObjectActivatedEventArgs.cs index 195f62945..ac9685342 100644 --- a/Terminal.Gui/Views/TreeView/ObjectActivatedEventArgs.cs +++ b/Terminal.Gui/Views/TreeView/ObjectActivatedEventArgs.cs @@ -1,3 +1,4 @@ +#nullable disable namespace Terminal.Gui.Views; /// Event args for the event diff --git a/Terminal.Gui/Views/TreeView/SelectionChangedEventArgs.cs b/Terminal.Gui/Views/TreeView/SelectionChangedEventArgs.cs index 4b9eb93b9..d0efe0392 100644 --- a/Terminal.Gui/Views/TreeView/SelectionChangedEventArgs.cs +++ b/Terminal.Gui/Views/TreeView/SelectionChangedEventArgs.cs @@ -1,3 +1,4 @@ +#nullable disable namespace Terminal.Gui.Views; /// Event arguments describing a change in selected object in a tree view diff --git a/Terminal.Gui/Views/TreeView/TreeBuilder.cs b/Terminal.Gui/Views/TreeView/TreeBuilder.cs index afb7f5e18..f17e5a92c 100644 --- a/Terminal.Gui/Views/TreeView/TreeBuilder.cs +++ b/Terminal.Gui/Views/TreeView/TreeBuilder.cs @@ -1,3 +1,4 @@ +#nullable disable namespace Terminal.Gui.Views; /// Abstract implementation of . diff --git a/Terminal.Gui/Views/TreeView/TreeNode.cs b/Terminal.Gui/Views/TreeView/TreeNode.cs index 3377b92ac..750c1792b 100644 --- a/Terminal.Gui/Views/TreeView/TreeNode.cs +++ b/Terminal.Gui/Views/TreeView/TreeNode.cs @@ -1,3 +1,4 @@ +#nullable disable namespace Terminal.Gui.Views; /// diff --git a/Terminal.Gui/Views/TreeView/TreeNodeBuilder.cs b/Terminal.Gui/Views/TreeView/TreeNodeBuilder.cs index 08dc93108..f09775a53 100644 --- a/Terminal.Gui/Views/TreeView/TreeNodeBuilder.cs +++ b/Terminal.Gui/Views/TreeView/TreeNodeBuilder.cs @@ -1,3 +1,4 @@ +#nullable disable namespace Terminal.Gui.Views; /// implementation for objects diff --git a/Terminal.Gui/Views/TreeView/TreeStyle.cs b/Terminal.Gui/Views/TreeView/TreeStyle.cs index fe5eea7c4..14eb193f9 100644 --- a/Terminal.Gui/Views/TreeView/TreeStyle.cs +++ b/Terminal.Gui/Views/TreeView/TreeStyle.cs @@ -1,3 +1,4 @@ +#nullable disable  namespace Terminal.Gui.Views; diff --git a/Terminal.Gui/Views/TreeView/TreeView.cs b/Terminal.Gui/Views/TreeView/TreeView.cs index c640b4e25..fd33ad0ff 100644 --- a/Terminal.Gui/Views/TreeView/TreeView.cs +++ b/Terminal.Gui/Views/TreeView/TreeView.cs @@ -2,6 +2,7 @@ // by phillip.piper@gmail.com). Phillip has explicitly granted permission for his design // and code to be used in this library under the MIT license. +#nullable disable using System.Collections.ObjectModel; namespace Terminal.Gui.Views; diff --git a/Terminal.Gui/Views/TreeView/TreeViewTextFilter.cs b/Terminal.Gui/Views/TreeView/TreeViewTextFilter.cs index 4be43e00a..f79e6e674 100644 --- a/Terminal.Gui/Views/TreeView/TreeViewTextFilter.cs +++ b/Terminal.Gui/Views/TreeView/TreeViewTextFilter.cs @@ -1,3 +1,4 @@ +#nullable disable namespace Terminal.Gui.Views; /// diff --git a/Terminal.Gui/Views/View.cs b/Terminal.Gui/Views/View.cs deleted file mode 100644 index 0519ecba6..000000000 --- a/Terminal.Gui/Views/View.cs +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/Terminal.Gui/Views/Window.cs b/Terminal.Gui/Views/Window.cs index f2b51ce40..b374459a2 100644 --- a/Terminal.Gui/Views/Window.cs +++ b/Terminal.Gui/Views/Window.cs @@ -1,4 +1,4 @@ -#nullable enable + namespace Terminal.Gui.Views; diff --git a/Terminal.Gui/Views/Wizard/Wizard.cs b/Terminal.Gui/Views/Wizard/Wizard.cs index a9b011451..fe3cfcc63 100644 --- a/Terminal.Gui/Views/Wizard/Wizard.cs +++ b/Terminal.Gui/Views/Wizard/Wizard.cs @@ -1,5 +1,3 @@ -#nullable enable - namespace Terminal.Gui.Views; /// @@ -44,7 +42,7 @@ namespace Terminal.Gui.Views; /// Application.RequestStop(); /// }; /// -/// Application.Top.Add (wizard); +/// Application.Current.Add (wizard); /// Application.Run (); /// Application.Shutdown (); /// @@ -123,7 +121,7 @@ public class Wizard : Dialog /// /// /// If a non-Modal Wizard is added to the application after - /// has + /// has /// been called the first step must be explicitly set by setting to /// : /// @@ -374,7 +372,7 @@ public class Wizard : Dialog /// /// is derived from and Dialog causes Esc to call - /// , closing the Dialog. Wizard overrides + /// , closing the Dialog. Wizard overrides /// to instead fire the event when Wizard is being used as a /// non-modal (see ). /// diff --git a/Terminal.Gui/Views/Wizard/WizardEventArgs.cs b/Terminal.Gui/Views/Wizard/WizardEventArgs.cs index 85444fd30..4aac63162 100644 --- a/Terminal.Gui/Views/Wizard/WizardEventArgs.cs +++ b/Terminal.Gui/Views/Wizard/WizardEventArgs.cs @@ -1,3 +1,4 @@ +#nullable disable namespace Terminal.Gui.Views; /// for transition events. diff --git a/Terminal.Gui/Views/Wizard/WizardStep.cs b/Terminal.Gui/Views/Wizard/WizardStep.cs index 6e211b1e8..56dd09a94 100644 --- a/Terminal.Gui/Views/Wizard/WizardStep.cs +++ b/Terminal.Gui/Views/Wizard/WizardStep.cs @@ -1,4 +1,4 @@ -#nullable enable + namespace Terminal.Gui.Views; diff --git a/Terminal.sln b/Terminal.sln index 983bfdd29..050122e35 100644 --- a/Terminal.sln +++ b/Terminal.sln @@ -27,6 +27,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Settings", "Settings", "{B8 nuget.config = nuget.config Terminal.sln.DotSettings = Terminal.sln.DotSettings testenvironments.json = testenvironments.json + docfx\schemas\tui-config-schema.json = docfx\schemas\tui-config-schema.json EndProjectSection EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "GitHub", "GitHub", "{13BB2C46-B324-4B9C-92EB-CE6184D4736E}" @@ -45,11 +46,43 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "GitHub", "GitHub", "{13BB2C EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Docs", "Docs", "{C7A51224-5E0F-4986-AB37-A6BF89966C12}" ProjectSection(SolutionItems) = preProject + docfx\docs\ansiparser.md = docfx\docs\ansiparser.md + docfx\docs\application.md = docfx\docs\application.md + docfx\docs\arrangement.md = docfx\docs\arrangement.md + docfx\docs\cancellable-work-pattern.md = docfx\docs\cancellable-work-pattern.md + docfx\docs\CharacterMap.md = docfx\docs\CharacterMap.md .github\CODEOWNERS = .github\CODEOWNERS CODE_OF_CONDUCT.md = CODE_OF_CONDUCT.md + docfx\docs\command.md = docfx\docs\command.md + docfx\docs\config.md = docfx\docs\config.md CONTRIBUTING.md = CONTRIBUTING.md .github\copilot-instructions.md = .github\copilot-instructions.md + docfx\docs\cursor.md = docfx\docs\cursor.md + docfx\docs\dimauto.md = docfx\docs\dimauto.md + docfx\docs\drawing.md = docfx\docs\drawing.md + docfx\docs\drivers.md = docfx\docs\drivers.md + docfx\docs\events.md = docfx\docs\events.md + docfx\docs\getting-started.md = docfx\docs\getting-started.md + docfx\docs\index.md = docfx\docs\index.md + docfx\docs\keyboard.md = docfx\docs\keyboard.md + docfx\docs\layout.md = docfx\docs\layout.md + docfx\docs\lexicon.md = docfx\docs\lexicon.md + docfx\docs\logging.md = docfx\docs\logging.md + docfx\docs\migratingfromv1.md = docfx\docs\migratingfromv1.md + docfx\docs\mouse.md = docfx\docs\mouse.md + docfx\docs\multitasking.md = docfx\docs\multitasking.md + docfx\docs\navigation.md = docfx\docs\navigation.md + docfx\docs\newinv2.md = docfx\docs\newinv2.md + docfx\docs\Popovers.md = docfx\docs\Popovers.md README.md = README.md + docfx\docs\scheme.md = docfx\docs\scheme.md + docfx\docs\scrolling.md = docfx\docs\scrolling.md + docfx\docs\showcase.md = docfx\docs\showcase.md + docfx\docs\tableview.md = docfx\docs\tableview.md + docfx\docs\toc.yml = docfx\docs\toc.yml + docfx\docs\treeview.md = docfx\docs\treeview.md + docfx\docs\View.md = docfx\docs\View.md + docfx\docs\views.md = docfx\docs\views.md EndProjectSection EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SelfContained", "Examples\SelfContained\SelfContained.csproj", "{524DEA78-7E7C-474D-B42D-52ED4C04FF14}" @@ -163,10 +196,6 @@ Global {8C643A64-2A77-4432-987A-2E72BD9708E3}.Debug|Any CPU.Build.0 = Debug|Any CPU {8C643A64-2A77-4432-987A-2E72BD9708E3}.Release|Any CPU.ActiveCfg = Release|Any CPU {8C643A64-2A77-4432-987A-2E72BD9708E3}.Release|Any CPU.Build.0 = Release|Any CPU - {566AFB59-FF8C-FFF4-C1F4-049B6246E4A7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {566AFB59-FF8C-FFF4-C1F4-049B6246E4A7}.Debug|Any CPU.Build.0 = Debug|Any CPU - {566AFB59-FF8C-FFF4-C1F4-049B6246E4A7}.Release|Any CPU.ActiveCfg = Release|Any CPU - {566AFB59-FF8C-FFF4-C1F4-049B6246E4A7}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/Tests/IntegrationTests/FluentTests/FileDialogFluentTests.cs b/Tests/IntegrationTests/FluentTests/FileDialogFluentTests.cs index 770078bb3..6cc94ec45 100644 --- a/Tests/IntegrationTests/FluentTests/FileDialogFluentTests.cs +++ b/Tests/IntegrationTests/FluentTests/FileDialogFluentTests.cs @@ -191,7 +191,7 @@ public class FileDialogFluentTests SaveDialog? sd = null; MockFileSystem? fs = null; using var c = With.A (() => NewSaveDialog (out sd, out fs, modal: false), 100, 20, d) - .Then (() => sd!.Style.PreserveFilenameOnDirectoryChanges = true) + .Then ((_) => sd!.Style.PreserveFilenameOnDirectoryChanges = true) .ScreenShot ("Save dialog", _out) .AssertTrue (sd!.Canceled) .Focus (_ => true) @@ -230,7 +230,7 @@ public class FileDialogFluentTests SaveDialog? sd = null; MockFileSystem? fs = null; using var c = With.A (() => NewSaveDialog (out sd, out fs, modal: false), 100, 20, d) - .Then (() => sd!.Style.PreserveFilenameOnDirectoryChanges = false) + .Then ((_) => sd!.Style.PreserveFilenameOnDirectoryChanges = false) .ScreenShot ("Save dialog", _out) .AssertTrue (sd!.Canceled) .Focus (_ => true) @@ -267,7 +267,7 @@ public class FileDialogFluentTests SaveDialog? sd = null; MockFileSystem? fs = null; using var c = With.A (() => NewSaveDialog (out sd, out fs, modal: false), 100, 20, d) - .Then (() => sd!.Style.PreserveFilenameOnDirectoryChanges = preserve) + .Then ((_) => sd!.Style.PreserveFilenameOnDirectoryChanges = preserve) .ScreenShot ("Save dialog", _out) .AssertTrue (sd!.Canceled) .Focus (_ => true) diff --git a/Tests/IntegrationTests/FluentTests/GuiTestContextKeyEventTests.cs b/Tests/IntegrationTests/FluentTests/GuiTestContextKeyEventTests.cs index 9c7c9b0aa..51097c74c 100644 --- a/Tests/IntegrationTests/FluentTests/GuiTestContextKeyEventTests.cs +++ b/Tests/IntegrationTests/FluentTests/GuiTestContextKeyEventTests.cs @@ -16,10 +16,10 @@ public class GuiTestContextKeyEventTests (ITestOutputHelper outputHelper) public void QuitKey_ViaApplication_Stops (TestDriver d) { using GuiTestContext context = With.A (40, 10, d); - Assert.True (Application.Top!.Running); + Assert.True (context.App?.Current!.Running); - Toplevel top = Application.Top; - context.Then (() => Application.RaiseKeyDownEvent (Application.QuitKey)); + Toplevel? top = context.App?.Current; + context.Then ((_) => context!.App?.Keyboard.RaiseKeyDownEvent (Application.QuitKey)); Assert.False (top!.Running); } @@ -28,9 +28,9 @@ public class GuiTestContextKeyEventTests (ITestOutputHelper outputHelper) public void QuitKey_ViaEnqueueKey_Stops (TestDriver d) { using GuiTestContext context = With.A (40, 10, d, _out); - Assert.True (Application.Top!.Running); + Assert.True (context.App?.Current!.Running); - Toplevel top = Application.Top; + Toplevel? top = context.App?.Current; context.EnqueueKeyEvent (Application.QuitKey); Assert.False (top!.Running); @@ -46,7 +46,7 @@ public class GuiTestContextKeyEventTests (ITestOutputHelper outputHelper) using GuiTestContext context = With.A (40, 10, d, _out) .Add (view) - .Then (() => view.SetFocus ()) + .Then ((_) => view.SetFocus ()) .ResizeConsole (50, 20) .EnqueueKeyEvent (Key.A); @@ -62,7 +62,7 @@ public class GuiTestContextKeyEventTests (ITestOutputHelper outputHelper) using GuiTestContext context = With.A (40, 10, d, _out) .Add (textField) .Focus (textField) - .Then (() => textField.CursorPosition = textField.Text.Length) + .Then ((_) => textField.CursorPosition = textField.Text.Length) .EnqueueKeyEvent (Key.Backspace) .EnqueueKeyEvent (Key.Backspace); @@ -81,14 +81,14 @@ public class GuiTestContextKeyEventTests (ITestOutputHelper outputHelper) using GuiTestContext context = With.A (40, 10, d, _out) .Add (textField) .Add (button) - .Then (() => textField.SetFocus ()) + .Then ((_) => textField.SetFocus ()) .EnqueueKeyEvent (Key.T.WithShift) .EnqueueKeyEvent (Key.E) .EnqueueKeyEvent (Key.S) .EnqueueKeyEvent (Key.T) .AssertEqual ("Test", textField.Text) .EnqueueKeyEvent (Key.Tab) - .Then (() => Assert.True (button.HasFocus)) + .Then ((_) => Assert.True (button.HasFocus)) .EnqueueKeyEvent (Key.Enter) .AssertEqual (1, clickedCount); } @@ -110,7 +110,7 @@ public class GuiTestContextKeyEventTests (ITestOutputHelper outputHelper) using GuiTestContext context = With.A (40, 10, d, _out) .Add (view) - .Then (() => view.SetFocus ()) + .Then ((_) => view.SetFocus ()) .EnqueueKeyEvent (Key.A); Assert.True (keyReceived, "Key was not received by the view"); @@ -128,7 +128,7 @@ public class GuiTestContextKeyEventTests (ITestOutputHelper outputHelper) using GuiTestContext context = With.A (40, 10, d, _out) .Add (view) - .Then (() => view.SetFocus ()) + .Then ((_) => view.SetFocus ()) .EnqueueKeyEvent (Key.F1) .EnqueueKeyEvent (Key.F5) .EnqueueKeyEvent (Key.F12); @@ -150,7 +150,7 @@ public class GuiTestContextKeyEventTests (ITestOutputHelper outputHelper) using GuiTestContext context = With.A (40, 10, d, _out) .Add (view) - .Then (() => view.SetFocus ()) + .Then ((_) => view.SetFocus ()) .EnqueueKeyEvent (Key.A) .EnqueueKeyEvent (Key.B) .EnqueueKeyEvent (Key.C); @@ -171,7 +171,7 @@ public class GuiTestContextKeyEventTests (ITestOutputHelper outputHelper) using GuiTestContext context = With.A (40, 10, d, _out) .Add (view1) .Add (view2) - .Then (() => view1.SetFocus ()) + .Then ((_) => view1.SetFocus ()) .AssertTrue (view1.HasFocus) .AssertFalse (view2.HasFocus) .EnqueueKeyEvent (Key.Tab) @@ -190,7 +190,7 @@ public class GuiTestContextKeyEventTests (ITestOutputHelper outputHelper) using GuiTestContext context = With.A (40, 10, d, _out) .Add (textField) - .Then (() => textField.SetFocus ()) + .Then ((_) => textField.SetFocus ()) .EnqueueKeyEvent (Key.D1) .EnqueueKeyEvent (Key.D2) .EnqueueKeyEvent (Key.D3) @@ -210,7 +210,7 @@ public class GuiTestContextKeyEventTests (ITestOutputHelper outputHelper) using GuiTestContext context = With.A (40, 10, d, _out) .Add (view) - .Then (() => view.SetFocus ()); + .Then ((_) => view.SetFocus ()); // Send 10 keys rapidly for (var i = 0; i < 10; i++) @@ -237,7 +237,7 @@ public class GuiTestContextKeyEventTests (ITestOutputHelper outputHelper) using GuiTestContext context = With.A (40, 10, d, _out) .Add (view) - .Then (() => view.SetFocus ()) + .Then ((_) => view.SetFocus ()) .EnqueueKeyEvent (Key.Enter) .EnqueueKeyEvent (Key.Tab) .EnqueueKeyEvent (Key.CursorUp) @@ -266,7 +266,7 @@ public class GuiTestContextKeyEventTests (ITestOutputHelper outputHelper) using GuiTestContext context = With.A (40, 10, d, _out) .Add (listView) - .Then (() => listView.SetFocus ()) + .Then ((_) => listView.SetFocus ()) .AssertEqual (0, listView.SelectedItem) .EnqueueKeyEvent (Key.CursorDown) .AssertEqual (1, listView.SelectedItem) @@ -293,7 +293,7 @@ public class GuiTestContextKeyEventTests (ITestOutputHelper outputHelper) using GuiTestContext context = With.A (40, 10, d, _out) .Add (view) - .Then (() => view.SetFocus ()) + .Then ((_) => view.SetFocus ()) .EnqueueKeyEvent (Key.A.WithCtrl); Assert.True (keyReceived); diff --git a/Tests/IntegrationTests/FluentTests/GuiTestContextMouseEventTests.cs b/Tests/IntegrationTests/FluentTests/GuiTestContextMouseEventTests.cs index a68c17153..073852944 100644 --- a/Tests/IntegrationTests/FluentTests/GuiTestContextMouseEventTests.cs +++ b/Tests/IntegrationTests/FluentTests/GuiTestContextMouseEventTests.cs @@ -152,7 +152,7 @@ public class GuiTestContextMouseEventTests (ITestOutputHelper outputHelper) using GuiTestContext context = With.A (40, 10, d, _out) .Add (view1) .Add (view2) - .Then (() => view1.SetFocus ()) + .Then ((_) => view1.SetFocus ()) .AssertTrue (view1.HasFocus) .LeftClick (25, 7) // Click on view2 .AssertFalse (view1.HasFocus) diff --git a/Tests/IntegrationTests/FluentTests/GuiTestContextTests.cs b/Tests/IntegrationTests/FluentTests/GuiTestContextTests.cs index 91c0fb3dc..30ea80cd8 100644 --- a/Tests/IntegrationTests/FluentTests/GuiTestContextTests.cs +++ b/Tests/IntegrationTests/FluentTests/GuiTestContextTests.cs @@ -43,8 +43,24 @@ public class GuiTestContextTests (ITestOutputHelper outputHelper) public void With_New_A_Runs (TestDriver d) { using GuiTestContext context = With.A (40, 10, d, _out); - Assert.True (Application.Top!.Running); - Assert.NotEqual (Rectangle.Empty, Application.Screen); + Assert.True (context.App!.Current!.Running); + Assert.NotEqual (Rectangle.Empty, context.App!.Screen); + } + + [Theory] + [ClassData (typeof (TestDrivers))] + public void AnsiScreenShot_Renders_Ansi_Stream (TestDriver d) + { + using GuiTestContext context = With.A (10, 3, d, _out) + .Then ((app) => + { + app.Current!.BorderStyle = LineStyle.None; + app.Current!.Border!.Thickness = Thickness.Empty; + app.Current.Text = "hello"; + }) + .ScreenShot ("ScreenShot", _out) + .AnsiScreenShot ("AnsiScreenShot", _out) +; } [Theory] diff --git a/Tests/IntegrationTests/FluentTests/MenuBarv2Tests.cs b/Tests/IntegrationTests/FluentTests/MenuBarv2Tests.cs index 597214929..bc96b6f95 100644 --- a/Tests/IntegrationTests/FluentTests/MenuBarv2Tests.cs +++ b/Tests/IntegrationTests/FluentTests/MenuBarv2Tests.cs @@ -24,7 +24,7 @@ public class MenuBarv2Tests public void Initializes_WithNoItems (TestDriver d) { using GuiTestContext c = With.A (80, 25, d, _out) - .Then (() => + .Then ((_) => { // Create a menu bar with no items var menuBar = new MenuBarv2 (); @@ -42,7 +42,7 @@ public class MenuBarv2Tests MenuBarItemv2 [] menuItems = []; using GuiTestContext c = With.A (80, 25, d, _out) - .Then (() => + .Then ((_) => { // Create items for the menu bar menuItems = @@ -79,7 +79,7 @@ public class MenuBarv2Tests public void AddsItems_WithMenusProperty (TestDriver d) { using GuiTestContext c = With.A (80, 25, d, _out) - .Then (() => + .Then ((_) => { var menuBar = new MenuBarv2 (); @@ -100,7 +100,7 @@ public class MenuBarv2Tests public void ChangesKey_RaisesEvent (TestDriver d) { using GuiTestContext c = With.A (80, 25, d, _out) - .Then (() => + .Then ((_) => { var menuBar = new MenuBarv2 (); @@ -137,12 +137,13 @@ public class MenuBarv2Tests public void DefaultKey_Activates (TestDriver d) { MenuBarv2? menuBar = null; + Toplevel? top = null; using GuiTestContext c = With.A (50, 20, d, _out) - .Then (() => + .Then ((app) => { menuBar = new MenuBarv2 (); - Toplevel top = Application.Top!; + top = app.Current!; top.Add ( new View () @@ -152,16 +153,16 @@ public class MenuBarv2Tests }); menuBar.EnableForDesign (ref top); - Application.Top!.Add (menuBar); + app.Current!.Add (menuBar); }) .WaitIteration () - .AssertIsNotType (Application.Navigation!.GetFocused ()) + .AssertIsNotType (top?.App?.Navigation!.GetFocused ()) .ScreenShot ("MenuBar initial state", _out) .EnqueueKeyEvent (MenuBarv2.DefaultKey) .WaitIteration () .ScreenShot ($"After {MenuBarv2.DefaultKey}", _out) - .AssertEqual ("_New file", Application.Navigation!.GetFocused ()!.Title) - .AssertTrue (Application.Popover?.GetActivePopover () is PopoverMenu) + .AssertEqual ("_New file", top?.App?.Navigation!.GetFocused ()!.Title) + .AssertTrue (top?.App?.Popover?.GetActivePopover () is PopoverMenu) .AssertTrue (menuBar?.IsOpen ()); } @@ -171,12 +172,13 @@ public class MenuBarv2Tests public void DefaultKey_DeActivates (TestDriver d) { MenuBarv2? menuBar = null; - + IApplication? app = null; using GuiTestContext c = With.A (50, 20, d, _out) - .Then (() => + .Then ((a) => { + app = a; menuBar = new MenuBarv2 (); - Toplevel top = Application.Top!; + Toplevel top = app.Current!; top.Add ( new View () @@ -186,26 +188,28 @@ public class MenuBarv2Tests }); menuBar.EnableForDesign (ref top); - Application.Top!.Add (menuBar); + app.Current!.Add (menuBar); }) .WaitIteration () - .AssertIsNotType (Application.Navigation!.GetFocused ()) + .AssertIsNotType (app?.Navigation!.GetFocused ()) .ScreenShot ("MenuBar initial state", _out) .EnqueueKeyEvent (MenuBarv2.DefaultKey) .ScreenShot ($"After {MenuBarv2.DefaultKey}", _out) - .AssertEqual ("_New file", Application.Navigation!.GetFocused ()!.Title) + .AssertEqual ("_New file", app?.Navigation!.GetFocused ()!.Title) .EnqueueKeyEvent (MenuBarv2.DefaultKey) .ScreenShot ($"After {MenuBarv2.DefaultKey}", _out) - .AssertIsNotType (Application.Navigation!.GetFocused ()); + .AssertIsNotType (app?.Navigation!.GetFocused ()); } [Theory] [ClassData (typeof (TestDrivers))] public void ShowHidePopovers (TestDriver d) { + IApplication? app = null; using GuiTestContext c = With.A (80, 25, d, _out) - .Then (() => + .Then ((a) => { + app = a; // Create a menu bar with items that have submenus var fileMenuItem = new MenuBarItemv2 ( "_File", @@ -214,7 +218,7 @@ public class MenuBarv2Tests new MenuItemv2 ("_Save", string.Empty, null) ]); - var menuBar = new MenuBarv2 ([fileMenuItem]); + var menuBar = new MenuBarv2 ([fileMenuItem]) { App = app }; // Initially, no menu should be open Assert.False (menuBar.IsOpen ()); @@ -259,13 +263,13 @@ public class MenuBarv2Tests public void EnableForDesign_CreatesMenuItems (TestDriver d) { using GuiTestContext c = With.A (80, 25, d, _out) - .Then (() => + .Then ((app) => { var menuBar = new MenuBarv2 (); - Application.Top!.Add (menuBar); + app.Current!.Add (menuBar); // Call EnableForDesign - Toplevel top = Application.Top!; + Toplevel top = app.Current!; bool result = menuBar.EnableForDesign (ref top); // Should return true @@ -291,36 +295,38 @@ public class MenuBarv2Tests public void Navigation_Left_Right_Wraps (TestDriver d) { MenuBarv2? menuBar = null; + IApplication? app = null; using GuiTestContext c = With.A (50, 20, d, _out) - .Then (() => + .Then ((a) => { + app = a; menuBar = new MenuBarv2 (); - Toplevel top = Application.Top!; + Toplevel top = app.Current!; menuBar.EnableForDesign (ref top); - Application.Top!.Add (menuBar); + app.Current!.Add (menuBar); }) .WaitIteration () .ScreenShot ("MenuBar initial state", _out) .EnqueueKeyEvent (MenuBarv2.DefaultKey) - .AssertTrue (Application.Popover?.GetActivePopover () is PopoverMenu) + .AssertTrue (app?.Popover?.GetActivePopover () is PopoverMenu) .AssertTrue (menuBar?.IsOpen ()) - .AssertEqual ("_New file", Application.Navigation?.GetFocused ()!.Title) + .AssertEqual ("_New file", app?.Navigation?.GetFocused ()!.Title) .ScreenShot ($"After {MenuBarv2.DefaultKey}", _out) .EnqueueKeyEvent (Key.CursorRight) - .AssertTrue (Application.Popover?.GetActivePopover () is PopoverMenu) + .AssertTrue (app?.Popover?.GetActivePopover () is PopoverMenu) .ScreenShot ("After right arrow", _out) - .AssertEqual ("Cu_t", Application.Navigation?.GetFocused ()!.Title) + .AssertEqual ("Cu_t", app?.Navigation?.GetFocused ()!.Title) .EnqueueKeyEvent (Key.CursorRight) .ScreenShot ("After second right arrow", _out) - .AssertEqual ("_Online Help...", Application.Navigation?.GetFocused ()!.Title) + .AssertEqual ("_Online Help...", app?.Navigation?.GetFocused ()!.Title) .ScreenShot ("After third right arrow", _out) .EnqueueKeyEvent (Key.CursorRight) .ScreenShot ("After fourth right arrow", _out) - .AssertEqual ("_New file", Application.Navigation?.GetFocused ()!.Title) + .AssertEqual ("_New file", app?.Navigation?.GetFocused ()!.Title) .EnqueueKeyEvent (Key.CursorLeft) .ScreenShot ("After left arrow", _out) - .AssertEqual ("_Online Help...", Application.Navigation?.GetFocused ()!.Title); + .AssertEqual ("_Online Help...", app?.Navigation?.GetFocused ()!.Title); } @@ -329,12 +335,14 @@ public class MenuBarv2Tests public void MenuBarItem_With_QuitKey_Open_QuitKey_Restores_Focus_Correctly (TestDriver d) { MenuBarv2? menuBar = null; + IApplication? app = null; using GuiTestContext c = With.A (50, 20, d, _out) - .Then (() => + .Then ((a) => { + app = a; menuBar = new MenuBarv2 (); - Toplevel top = Application.Top!; + Toplevel top = app.Current!; top.Add ( new View () @@ -344,19 +352,19 @@ public class MenuBarv2Tests }); menuBar.EnableForDesign (ref top); - Application.Top!.Add (menuBar); + app.Current!.Add (menuBar); }) - .AssertIsNotType (Application.Navigation!.GetFocused ()) + .AssertIsNotType (app!.Navigation!.GetFocused ()) .ScreenShot ("MenuBar initial state", _out) .EnqueueKeyEvent (MenuBarv2.DefaultKey) - .AssertEqual ("_New file", Application.Navigation!.GetFocused ()!.Title) - .AssertTrue (Application.Popover?.GetActivePopover () is PopoverMenu) + .AssertEqual ("_New file", app.Navigation!.GetFocused ()!.Title) + .AssertTrue (app?.Popover?.GetActivePopover () is PopoverMenu) .AssertTrue (menuBar?.IsOpen ()) - .AssertEqual ("_New file", Application.Navigation?.GetFocused ()!.Title) + .AssertEqual ("_New file", app?.Navigation?.GetFocused ()!.Title) .ScreenShot ($"After {MenuBarv2.DefaultKey}", _out) .EnqueueKeyEvent (Application.QuitKey) - .AssertFalse (Application.Popover?.GetActivePopover () is PopoverMenu) - .AssertIsNotType (Application.Navigation!.GetFocused ()); + .AssertFalse (app?.Popover?.GetActivePopover () is PopoverMenu) + .AssertIsNotType (app!.Navigation!.GetFocused ()); } [Theory] @@ -364,6 +372,7 @@ public class MenuBarv2Tests public void MenuBarItem_Without_QuitKey_Open_QuitKey_Restores_Focus_Correctly (TestDriver d) { MenuBarv2? menuBar = null; + IApplication? app = null; using GuiTestContext c = With.A (50, 20, d, _out) .Add ( @@ -373,26 +382,27 @@ public class MenuBarv2Tests Id = "focusableView", }) - .Then (() => + .Then ((a) => { + app = a; menuBar = new MenuBarv2 (); - Toplevel? toplevel = Application.Top; + Toplevel? toplevel = app.Current; menuBar.EnableForDesign (ref toplevel!); - Application.Top!.Add (menuBar); + app.Current!.Add (menuBar); }) .WaitIteration () - .AssertIsNotType (Application.Navigation!.GetFocused ()) + .AssertIsNotType (app?.Navigation!.GetFocused ()) .ScreenShot ("MenuBar initial state", _out) .EnqueueKeyEvent (MenuBarv2.DefaultKey) .EnqueueKeyEvent (Key.CursorRight) - .AssertEqual ("Cu_t", Application.Navigation!.GetFocused ()!.Title) - .AssertTrue (Application.Popover?.GetActivePopover () is PopoverMenu) + .AssertEqual ("Cu_t", app?.Navigation!.GetFocused ()!.Title) + .AssertTrue (app?.Popover?.GetActivePopover () is PopoverMenu) .AssertTrue (menuBar?.IsOpen ()) - .AssertEqual ("Cu_t", Application.Navigation?.GetFocused ()!.Title) + .AssertEqual ("Cu_t", app?.Navigation?.GetFocused ()!.Title) .ScreenShot ($"After {MenuBarv2.DefaultKey}", _out) .EnqueueKeyEvent (Application.QuitKey) - .AssertFalse (Application.Popover?.GetActivePopover () is PopoverMenu) - .AssertIsNotType (Application.Navigation?.GetFocused ()); + .AssertFalse (app?.Popover?.GetActivePopover () is PopoverMenu) + .AssertIsNotType (app?.Navigation?.GetFocused ()); } [Theory] @@ -400,12 +410,14 @@ public class MenuBarv2Tests public void MenuBarItem_With_QuitKey_Open_QuitKey_Does_Not_Quit_App (TestDriver d) { MenuBarv2? menuBar = null; + IApplication? app = null; using GuiTestContext c = With.A (50, 20, d, _out) - .Then (() => + .Then ((a) => { + app = a; menuBar = new MenuBarv2 (); - Toplevel top = Application.Top!; + Toplevel top = app.Current!; top.Add ( new View () @@ -415,18 +427,18 @@ public class MenuBarv2Tests }); menuBar.EnableForDesign (ref top); - Application.Top!.Add (menuBar); + app.Current!.Add (menuBar); }) .WaitIteration () - .AssertIsNotType (Application.Navigation!.GetFocused ()) + .AssertIsNotType (app!.Navigation!.GetFocused ()) .ScreenShot ("MenuBar initial state", _out) .EnqueueKeyEvent (MenuBarv2.DefaultKey) - .AssertEqual ("_New file", Application.Navigation!.GetFocused ()!.Title) - .AssertTrue (Application.Top!.Running) + .AssertEqual ("_New file", app.Navigation!.GetFocused ()!.Title) + .AssertTrue (app?.Current!.Running) .ScreenShot ($"After {MenuBarv2.DefaultKey}", _out) .EnqueueKeyEvent (Application.QuitKey) - .AssertFalse (Application.Popover?.GetActivePopover () is PopoverMenu) - .AssertTrue (Application.Top!.Running); + .AssertFalse (app?.Popover?.GetActivePopover () is PopoverMenu) + .AssertTrue (app!.Current!.Running); } [Theory] @@ -434,12 +446,14 @@ public class MenuBarv2Tests public void MenuBarItem_Without_QuitKey_Open_QuitKey_Does_Not_Quit_MenuBar_SuperView (TestDriver d) { MenuBarv2? menuBar = null; + IApplication? app = null; using GuiTestContext c = With.A (50, 20, d, _out) - .Then (() => + .Then ((a) => { + app = a; menuBar = new MenuBarv2 (); - Toplevel top = Application.Top!; + Toplevel top = app.Current!; top.Add ( new View () @@ -456,17 +470,17 @@ public class MenuBarv2Tests item.Key = Key.Empty; } - Application.Top!.Add (menuBar); + app.Current!.Add (menuBar); }) .WaitIteration () - .AssertIsNotType (Application.Navigation!.GetFocused ()) + .AssertIsNotType (app?.Navigation!.GetFocused ()) .ScreenShot ("MenuBar initial state", _out) .EnqueueKeyEvent (MenuBarv2.DefaultKey) - .AssertEqual ("_New file", Application.Navigation!.GetFocused ()!.Title) + .AssertEqual ("_New file", app?.Navigation!.GetFocused ()!.Title) .ScreenShot ($"After {MenuBarv2.DefaultKey}", _out) .EnqueueKeyEvent (Application.QuitKey) - .AssertFalse (Application.Popover?.GetActivePopover () is PopoverMenu) - .AssertTrue (Application.Top!.Running); + .AssertFalse (app?.Popover?.GetActivePopover () is PopoverMenu) + .AssertTrue (app?.Current!.Running); } [Theory] @@ -489,12 +503,12 @@ public class MenuBarv2Tests }; using GuiTestContext c = With.A (50, 20, d, _out) - .Then (() => + .Then ((a) => { var menuBar = new MenuBarv2 (); - Toplevel top = Application.Top!; + Toplevel top = a.Current!; menuBar.EnableForDesign (ref top); - Application.Top!.Add (menuBar); + a.Current!.Add (menuBar); }) .Add (testView) .WaitIteration () @@ -523,12 +537,12 @@ public class MenuBarv2Tests }; using GuiTestContext c = With.A (50, 20, d, _out) - .Then (() => + .Then ((a) => { var menuBar = new MenuBarv2 (); - Toplevel top = Application.Top!; + Toplevel top = a.Current!; menuBar.EnableForDesign (ref top); - Application.Top!.Add (menuBar); + a.Current!.Add (menuBar); }) .Add (testView) .WaitIteration () diff --git a/Tests/IntegrationTests/FluentTests/NavigationTests.cs b/Tests/IntegrationTests/FluentTests/NavigationTests.cs index 96883edcc..fe74c59ea 100644 --- a/Tests/IntegrationTests/FluentTests/NavigationTests.cs +++ b/Tests/IntegrationTests/FluentTests/NavigationTests.cs @@ -21,7 +21,7 @@ public class NavigationTests (ITestOutputHelper outputHelper) var v6 = new View { Id = "v6", CanFocus = true }; using GuiTestContext c = With.A (50, 20, d, _out) - .Then (() => + .Then ((app) => { var w1 = new Window { Id = "w1" }; w1.Add (v1, v2); @@ -29,8 +29,8 @@ public class NavigationTests (ITestOutputHelper outputHelper) w2.Add (v3, v4); var w3 = new Window { Id = "w3" }; w3.Add (v5, v6); - Toplevel top = Application.Top!; - Application.Top!.Add (w1, w2, w3); + Toplevel top = app?.Current!; + app?.Current!.Add (w1, w2, w3); }) .AssertTrue (v5.HasFocus) .EnqueueKeyEvent (Key.F6) diff --git a/Tests/IntegrationTests/FluentTests/PopverMenuTests.cs b/Tests/IntegrationTests/FluentTests/PopverMenuTests.cs index d163a455b..83a173944 100644 --- a/Tests/IntegrationTests/FluentTests/PopverMenuTests.cs +++ b/Tests/IntegrationTests/FluentTests/PopverMenuTests.cs @@ -23,13 +23,13 @@ public class PopoverMenuTests public void EnableForDesign_CreatesMenuItems (TestDriver d) { using GuiTestContext c = With.A (80, 25, d) - .Then (() => + .Then ((app) => { PopoverMenu popoverMenu = new (); - Application.Top!.Add (popoverMenu); + app.Current!.Add (popoverMenu); // Call EnableForDesign - Toplevel top = Application.Top; + Toplevel top = app.Current; bool result = popoverMenu.EnableForDesign (ref top); // Should return true @@ -54,13 +54,18 @@ public class PopoverMenuTests { lock (o) { + IApplication? app = null; using GuiTestContext c = With.A (50, 20, d) - .Then (() => + .Then ((a) => { - PopoverMenu popoverMenu = new (); + app = a; + PopoverMenu popoverMenu = new () + { + App = app + }; // Call EnableForDesign - Toplevel top = Application.Top!; + Toplevel top = app.Current!; popoverMenu.EnableForDesign (ref top); var view = new View @@ -71,22 +76,22 @@ public class PopoverMenuTests Id = "focusableView", Text = "View" }; - Application.Top!.Add (view); + app.Current!.Add (view); // EnableForDesign sets to true; undo that popoverMenu.Visible = false; - Application.Popover!.Register (popoverMenu); + app?.Popover!.Register (popoverMenu); view.SetFocus (); }) - .AssertFalse (Application.Popover?.GetActivePopover () is PopoverMenu) - .AssertIsNotType (Application.Navigation!.GetFocused ()) + .AssertFalse (app?.Popover?.GetActivePopover () is PopoverMenu) + .AssertIsNotType (app?.Navigation!.GetFocused ()) .ScreenShot ("PopoverMenu initial state", _out) - .Then (() => Application.Popover!.Show (Application.Popover.Popovers.First ())) + .Then ((_) => app?.Popover!.Show (app?.Popover.Popovers.First ())) .ScreenShot ("After Show", _out) - .AssertTrue (Application.Popover?.GetActivePopover () is PopoverMenu) - .AssertEqual ("Cu_t", Application.Navigation!.GetFocused ()!.Title); + .AssertTrue (app?.Popover?.GetActivePopover () is PopoverMenu) + .AssertEqual ("Cu_t", app?.Navigation!.GetFocused ()!.Title); } } @@ -94,13 +99,18 @@ public class PopoverMenuTests [ClassData (typeof (TestDrivers))] public void QuitKey_Hides (TestDriver d) { + IApplication? app = null; using GuiTestContext c = With.A (50, 20, d) - .Then (() => + .Then ((a) => { - PopoverMenu popoverMenu = new (); + app = a; + PopoverMenu popoverMenu = new () + { + App = app + }; // Call EnableForDesign - Toplevel top = Application.Top!; + Toplevel top = app.Current!; bool result = popoverMenu.EnableForDesign (ref top); var view = new View @@ -111,38 +121,43 @@ public class PopoverMenuTests Id = "focusableView", Text = "View" }; - Application.Top!.Add (view); + app.Current!.Add (view); // EnableForDesign sets to true; undo that popoverMenu.Visible = false; - Application.Popover!.Register (popoverMenu); + app?.Popover!.Register (popoverMenu); view.SetFocus (); }) .ScreenShot ("PopoverMenu initial state", _out) - .AssertFalse (Application.Popover?.GetActivePopover () is PopoverMenu) - .Then (() => Application.Popover!.Show (Application.Popover.Popovers.First ())) + .AssertFalse (app?.Popover?.GetActivePopover () is PopoverMenu) + .Then ((_) => app?.Popover!.Show (app?.Popover.Popovers.First ())) .ScreenShot ("After Show", _out) - .AssertTrue (Application.Popover?.GetActivePopover () is PopoverMenu) + .AssertTrue (app?.Popover?.GetActivePopover () is PopoverMenu) .EnqueueKeyEvent (Application.QuitKey) .ScreenShot ($"After {Application.QuitKey}", _out) - .AssertFalse (Application.Popover!.Popovers.Cast ().FirstOrDefault ()!.Visible) - .AssertNull (Application.Popover!.GetActivePopover ()) - .AssertTrue (Application.Top!.Running); + .AssertFalse (app?.Popover!.Popovers.Cast ().FirstOrDefault ()!.Visible) + .AssertNull (app?.Popover!.GetActivePopover ()) + .AssertTrue (app?.Current!.Running); } [Theory] [ClassData (typeof (TestDrivers))] public void QuitKey_Restores_Focus_Correctly (TestDriver d) { + IApplication? app = null; using GuiTestContext c = With.A (50, 20, d) - .Then (() => + .Then ((a) => { - PopoverMenu popoverMenu = new (); + app = a; + PopoverMenu popoverMenu = new () + { + App = app + }; // Call EnableForDesign - Toplevel top = Application.Top!; + Toplevel top = app.Current!; bool result = popoverMenu.EnableForDesign (ref top); var view = new View @@ -153,39 +168,45 @@ public class PopoverMenuTests Id = "focusableView", Text = "View" }; - Application.Top!.Add (view); + app.Current!.Add (view); // EnableForDesign sets to true; undo that popoverMenu.Visible = false; - Application.Popover!.Register (popoverMenu); + app?.Popover!.Register (popoverMenu); view.SetFocus (); }) .ScreenShot ("PopoverMenu initial state", _out) - .AssertFalse (Application.Popover?.GetActivePopover () is PopoverMenu) - .AssertIsNotType (Application.Navigation!.GetFocused ()) - .Then (() => Application.Popover!.Show (Application.Popover.Popovers.First ())) + .AssertFalse (app?.Popover?.GetActivePopover () is PopoverMenu) + .AssertIsNotType (app?.Navigation!.GetFocused ()) + .Then ((_) => app?.Popover!.Show (app?.Popover.Popovers.First ())) .ScreenShot ("After Show", _out) - .AssertTrue (Application.Popover?.GetActivePopover () is PopoverMenu) - .AssertIsType (Application.Navigation!.GetFocused ()) + .AssertTrue (app?.Popover?.GetActivePopover () is PopoverMenu) + .AssertIsType (app?.Navigation!.GetFocused ()) .EnqueueKeyEvent (Application.QuitKey) .ScreenShot ($"After {Application.QuitKey}", _out) - .AssertFalse (Application.Popover?.GetActivePopover () is PopoverMenu) - .AssertIsNotType (Application.Navigation!.GetFocused ()); + .AssertFalse (app?.Popover?.GetActivePopover () is PopoverMenu) + .AssertIsNotType (app?.Navigation!.GetFocused ()); } [Theory] [ClassData (typeof (TestDrivers))] public void MenuBarItem_With_QuitKey_Open_QuitKey_Does_Not_Quit_App (TestDriver d) { + IApplication? app = null; + using GuiTestContext c = With.A (50, 20, d) - .Then (() => + .Then ((a) => { - PopoverMenu popoverMenu = new (); + app = a; + PopoverMenu popoverMenu = new () + { + App = app + }; // Call EnableForDesign - Toplevel top = Application.Top!; + Toplevel top = app.Current!; bool result = popoverMenu.EnableForDesign (ref top); var view = new View @@ -196,25 +217,25 @@ public class PopoverMenuTests Id = "focusableView", Text = "View" }; - Application.Top!.Add (view); + app.Current!.Add (view); // EnableForDesign sets to true; undo that popoverMenu.Visible = false; - Application.Popover!.Register (popoverMenu); + app?.Popover!.Register (popoverMenu); view.SetFocus (); }) - .AssertIsNotType (Application.Navigation!.GetFocused ()) + .AssertIsNotType (app?.Navigation!.GetFocused ()) .ScreenShot ("PopoverMenu initial state", _out) - .Then (() => Application.Popover!.Show (Application.Popover.Popovers.First ())) + .Then ((_) => app?.Popover!.Show (app?.Popover.Popovers.First ())) .ScreenShot ("PopoverMenu after Show", _out) - .AssertEqual ("Cu_t", Application.Navigation!.GetFocused ()!.Title) - .AssertTrue (Application.Top!.Running) + .AssertEqual ("Cu_t", app?.Navigation!.GetFocused ()!.Title) + .AssertTrue (app?.Current!.Running) .EnqueueKeyEvent (Application.QuitKey) .ScreenShot ($"After {Application.QuitKey}", _out) - .AssertFalse (Application.Popover?.GetActivePopover () is PopoverMenu) - .AssertTrue (Application.Top!.Running); + .AssertFalse (app?.Popover?.GetActivePopover () is PopoverMenu) + .AssertTrue (app?.Current!.Running); } [Theory] @@ -237,13 +258,18 @@ public class PopoverMenuTests } }; + IApplication? app = null; using GuiTestContext c = With.A (50, 20, d) - .Then (() => + .Then ((a) => { - PopoverMenu popoverMenu = new (); - Toplevel top = Application.Top!; + app = a; + PopoverMenu popoverMenu = new () + { + App = app + }; + Toplevel top = app.Current!; popoverMenu.EnableForDesign (ref top); - Application.Popover!.Register (popoverMenu); + app?.Popover!.Register (popoverMenu); }) .Add (testView) .Focus (testView) @@ -271,13 +297,19 @@ public class PopoverMenuTests } }; + IApplication? app = null; + using GuiTestContext c = With.A (50, 20, d) - .Then (() => + .Then ((a) => { - PopoverMenu popoverMenu = new (); - Toplevel top = Application.Top!; + app = a; + PopoverMenu popoverMenu = new () + { + App = app + }; + Toplevel top = app.Current!; popoverMenu.EnableForDesign (ref top); - Application.Popover!.Register (popoverMenu); + app?.Popover!.Register (popoverMenu); }) .Add (testView) .Focus (testView) @@ -305,13 +337,18 @@ public class PopoverMenuTests } }; + IApplication? app = null; using GuiTestContext c = With.A (50, 20, d) - .Then (() => + .Then ((a) => { - PopoverMenu popoverMenu = new (); - Toplevel top = Application.Top!; + app = a; + PopoverMenu popoverMenu = new () + { + App = app + }; + Toplevel top = app.Current!; popoverMenu.EnableForDesign (ref top); - Application.Popover!.Register (popoverMenu); + app?.Popover!.Register (popoverMenu); }) .Add (testView) .EnqueueKeyEvent (Application.QuitKey) @@ -326,24 +363,25 @@ public class PopoverMenuTests MenuItemv2 [] menuItems = [new ("_New File", string.Empty, () => { clicked = true; })]; + IApplication? app = null; using GuiTestContext c = With.A (40, 10, d, _out) - .WithContextMenu (new (menuItems)) + .Then ((a) => app = a) + .WithContextMenu (new (menuItems) { App = app }) .ScreenShot ("Before open menu", _out) // Click in main area inside border .RightClick (1, 1) - .Then (() => + .Then ((_) => { // Test depends on menu having a border - IPopover? popover = Application.Popover!.GetActivePopover (); + IPopover? popover = app?.Popover!.GetActivePopover (); Assert.NotNull (popover); var popoverMenu = popover as PopoverMenu; popoverMenu!.Root!.BorderStyle = LineStyle.Single; }) .ScreenShot ("After open menu", _out) .LeftClick (2, 2) - ; - Assert.True (clicked); + .AssertTrue(clicked); } [Theory] @@ -374,8 +412,11 @@ public class PopoverMenuTests new ("Six", "", null) ]; + IApplication? app = null; + using GuiTestContext c = With.A (40, 10, d) - .WithContextMenu (new (menuItems)) + .Then ((a) => app = a) + .WithContextMenu (new (menuItems) { App = app }) .ScreenShot ("Before open menu", _out) // Click in main area inside border diff --git a/Tests/IntegrationTests/FluentTests/TreeViewFluentTests.cs b/Tests/IntegrationTests/FluentTests/TreeViewFluentTests.cs index 013bd32a5..01863f962 100644 --- a/Tests/IntegrationTests/FluentTests/TreeViewFluentTests.cs +++ b/Tests/IntegrationTests/FluentTests/TreeViewFluentTests.cs @@ -55,7 +55,7 @@ public class TreeViewFluentTests }) .AssertIsAssignableFrom (tv.SelectedObject) .Then ( - () => + (_) => { // Re order root.Children = [bike, car, lorry]; @@ -150,7 +150,7 @@ public class TreeViewFluentTests Assert.Equal (mrE, tv.GetObjectOnRow (8)); }) .Then ( - () => + (_) => { // Re order root.Children = [bike, car, lorry]; diff --git a/Tests/IntegrationTests/UICatalog/ScenarioTests.cs b/Tests/IntegrationTests/UICatalog/ScenarioTests.cs index a7943d4e5..69021ab15 100644 --- a/Tests/IntegrationTests/UICatalog/ScenarioTests.cs +++ b/Tests/IntegrationTests/UICatalog/ScenarioTests.cs @@ -20,8 +20,6 @@ public class ScenarioTests : TestsAllViews private readonly ITestOutputHelper _output; - private object? _timeoutLock; - /// /// This runs through all Scenarios defined in UI Catalog, calling Init, Setup, and Run. /// Should find any Scenarios which crash on load or do not respond to . @@ -37,18 +35,14 @@ public class ScenarioTests : TestsAllViews return; } - Assert.Null (_timeoutLock); - _timeoutLock = new (); - - ConfigurationManager.Disable (true); - - // If a previous test failed, this will ensure that the Application is in a clean state Application.ResetState (true); _output.WriteLine ($"Running Scenario '{scenarioType}'"); Scenario? scenario = null; var scenarioName = string.Empty; - object? timeout = null; + // Do not use Application.AddTimer for out-of-band watchdogs as + // they will be stopped by Shutdown/ResetState. + Timer? watchdogTimer = null; var timeoutFired = false; // Increase timeout for macOS - it's consistently slower @@ -90,14 +84,7 @@ public class ScenarioTests : TestsAllViews iterationHandlerRemoved = true; } - lock (_timeoutLock) - { - if (timeout is { }) - { - Application.RemoveTimeout (timeout); - timeout = null; - } - } + watchdogTimer?.Dispose (); scenario?.Dispose (); scenario = null; @@ -130,10 +117,8 @@ public class ScenarioTests : TestsAllViews Application.Iteration += OnApplicationOnIteration; initialized = true; - lock (_timeoutLock) - { - timeout = Application.AddTimeout (TimeSpan.FromMilliseconds (abortTime), ForceCloseCallback); - } + // Use a System.Threading.Timer for the watchdog to ensure it's not affected by Application.StopAllTimers + watchdogTimer = new Timer (_ => ForceCloseCallback (), null, (int)abortTime, System.Threading.Timeout.Infinite); } else { @@ -144,13 +129,9 @@ public class ScenarioTests : TestsAllViews } // If the scenario doesn't close within abortTime ms, this will force it to quit - bool ForceCloseCallback () + void ForceCloseCallback () { - lock (_timeoutLock) - { - timeoutFired = true; - timeout = null; - } + timeoutFired = true; _output.WriteLine ($"TIMEOUT FIRED for {scenarioName} after {abortTime}ms. Attempting graceful shutdown."); @@ -167,8 +148,6 @@ public class ScenarioTests : TestsAllViews { _output.WriteLine ($"Exception during timeout callback: {ex.Message}"); } - - return false; } void OnApplicationOnIteration (object? s, IterationEventArgs a) @@ -219,7 +198,7 @@ public class ScenarioTests : TestsAllViews List posNames = ["Percent", "AnchorEnd", "Center", "Absolute"]; List dimNames = ["Auto", "Percent", "Fill", "Absolute"]; - Application.Init (null, "fake"); + Application.Init ("fake"); var top = new Toplevel (); diff --git a/Tests/StressTests/ApplicationStressTests.cs b/Tests/StressTests/ApplicationStressTests.cs index d58b521c6..0feadf210 100644 --- a/Tests/StressTests/ApplicationStressTests.cs +++ b/Tests/StressTests/ApplicationStressTests.cs @@ -71,8 +71,8 @@ public class ApplicationStressTests { int tbNow = _tbCounter; - // Wait for Application.Top to be running to ensure timed events can be processed - while (Application.Top is null || Application.Top is { Running: false }) + // Wait for Application.Current to be running to ensure timed events can be processed + while (Application.Current is null || Application.Current is { Running: false }) { Thread.Sleep (1); } diff --git a/Tests/StressTests/ScenariosStressTests.cs b/Tests/StressTests/ScenariosStressTests.cs index 9e047f1ab..fead31b84 100644 --- a/Tests/StressTests/ScenariosStressTests.cs +++ b/Tests/StressTests/ScenariosStressTests.cs @@ -126,7 +126,7 @@ public class ScenariosStressTests void OnApplicationSessionBegun (object? sender, SessionTokenEventArgs e) { - // Get a list of all subviews under Application.Top (and their subviews, etc.) + // Get a list of all subviews under Application.Current (and their subviews, etc.) // and subscribe to their DrawComplete event void SubscribeAllSubViews (View view) { @@ -140,7 +140,7 @@ public class ScenariosStressTests } } - SubscribeAllSubViews (Application.Top!); + SubscribeAllSubViews (Application.Current!); } // If the scenario doesn't close within the abort time, this will force it to quit diff --git a/Tests/TerminalGuiFluentTesting/FakeDriver/FakeApplicationFactory.cs b/Tests/TerminalGuiFluentTesting/FakeDriver/FakeApplicationFactory.cs index 92f644bbb..7e51c3c74 100644 --- a/Tests/TerminalGuiFluentTesting/FakeDriver/FakeApplicationFactory.cs +++ b/Tests/TerminalGuiFluentTesting/FakeDriver/FakeApplicationFactory.cs @@ -21,17 +21,14 @@ public class FakeApplicationFactory FakeOutput output = new (); output.SetSize (80, 25); - IApplication origApp = ApplicationImpl.Instance; - SizeMonitorImpl sizeMonitor = new (output); ApplicationImpl impl = new (new FakeComponentFactory (fakeInput, output, sizeMonitor)); - - ApplicationImpl.ChangeInstance (impl); + ApplicationImpl.SetInstance (impl); // Initialize with a fake driver - impl.Init (null, "fake"); + impl.Init ("fake"); - return new FakeApplicationLifecycle (origApp, hardStopTokenSource); + return new FakeApplicationLifecycle (hardStopTokenSource); } } diff --git a/Tests/TerminalGuiFluentTesting/FakeDriver/FakeApplicationLifecycle.cs b/Tests/TerminalGuiFluentTesting/FakeDriver/FakeApplicationLifecycle.cs index 7bfbac632..5fced9338 100644 --- a/Tests/TerminalGuiFluentTesting/FakeDriver/FakeApplicationLifecycle.cs +++ b/Tests/TerminalGuiFluentTesting/FakeDriver/FakeApplicationLifecycle.cs @@ -5,17 +5,15 @@ namespace Terminal.Gui.Drivers; /// Implements a fake application lifecycle for testing purposes. Cleans up the application on dispose by cancelling /// the provided and shutting down the application. /// -/// /// -internal class FakeApplicationLifecycle (IApplication origApp, CancellationTokenSource hardStop) : IDisposable +internal class FakeApplicationLifecycle (CancellationTokenSource hardStop) : IDisposable { /// public void Dispose () { hardStop.Cancel (); - Application.Top?.Dispose (); + Application.Current?.Dispose (); Application.Shutdown (); - ApplicationImpl.ChangeInstance (origApp); } } diff --git a/Tests/TerminalGuiFluentTesting/GuiTestContext.ContextMenu.cs b/Tests/TerminalGuiFluentTesting/GuiTestContext.ContextMenu.cs index c6f45f510..359fd7a0a 100644 --- a/Tests/TerminalGuiFluentTesting/GuiTestContext.ContextMenu.cs +++ b/Tests/TerminalGuiFluentTesting/GuiTestContext.ContextMenu.cs @@ -1,3 +1,5 @@ +using System.Diagnostics; + #pragma warning disable CS1591 // Missing XML comment for publicly visible type or member namespace TerminalGuiFluentTesting; @@ -12,13 +14,17 @@ public partial class GuiTestContext /// public GuiTestContext WithContextMenu (PopoverMenu? contextMenu) { - LastView.MouseEvent += (s, e) => + if (contextMenu?.App is null) + { + Fail (@"PopoverMenu's must have their App property set."); + } + LastView.MouseEvent += (_, e) => { if (e.Flags.HasFlag (MouseFlags.Button3Clicked)) { // Registering with the PopoverManager will ensure that the context menu is closed when the view is no longer focused // and the context menu is disposed when it is closed. - Application.Popover?.Register (contextMenu); + App.Popover?.Register (contextMenu); contextMenu?.MakeVisible (e.ScreenPosition); } }; diff --git a/Tests/TerminalGuiFluentTesting/GuiTestContext.Input.cs b/Tests/TerminalGuiFluentTesting/GuiTestContext.Input.cs index 2d1021731..6a77c0a72 100644 --- a/Tests/TerminalGuiFluentTesting/GuiTestContext.Input.cs +++ b/Tests/TerminalGuiFluentTesting/GuiTestContext.Input.cs @@ -58,13 +58,13 @@ public partial class GuiTestContext private GuiTestContext EnqueueMouseEvent (MouseEventArgs mouseEvent) { // Enqueue the mouse event - WaitIteration (() => + WaitIteration ((app) => { - if (Application.Driver is { }) + if (app.Driver is { }) { mouseEvent.Position = mouseEvent.ScreenPosition; - Application.Driver.InputProcessor.EnqueueMouseEvent (mouseEvent); + app.Driver.InputProcessor.EnqueueMouseEvent (mouseEvent); } else { @@ -81,7 +81,7 @@ public partial class GuiTestContext { var screen = Point.Empty; - GuiTestContext ctx = WaitIteration (() => + GuiTestContext ctx = WaitIteration ((_) => { TView v = Find (evaluator); screen = v.ViewportToScreen (new Point (0, 0)); @@ -199,10 +199,10 @@ public partial class GuiTestContext // We do this by subscribing to the Driver.KeyDown event and waiting until it is raised. // This prevents the application from missing the key event if we enqueue it and immediately return. bool keyReceived = false; - if (_applicationImpl?.Driver is { }) + if (App?.Driver is { }) { - _applicationImpl.Driver.KeyDown += DriverOnKeyDown; - _applicationImpl.Driver.EnqueueKeyEvent (key); + App.Driver.KeyDown += DriverOnKeyDown; + App.Driver.EnqueueKeyEvent (key); WaitUntil (() => keyReceived); } else @@ -215,7 +215,7 @@ public partial class GuiTestContext void DriverOnKeyDown (object? sender, Key e) { - _applicationImpl.Driver.KeyDown -= DriverOnKeyDown; + App.Driver.KeyDown -= DriverOnKeyDown; keyReceived = true; } diff --git a/Tests/TerminalGuiFluentTesting/GuiTestContext.Navigation.cs b/Tests/TerminalGuiFluentTesting/GuiTestContext.Navigation.cs index 49bb1a9b5..9bd8f5bc9 100644 --- a/Tests/TerminalGuiFluentTesting/GuiTestContext.Navigation.cs +++ b/Tests/TerminalGuiFluentTesting/GuiTestContext.Navigation.cs @@ -40,13 +40,13 @@ public partial class GuiTestContext public GuiTestContext Focus (Func? evaluator = null) where T : View { evaluator ??= _ => true; - Toplevel? t = Application.Top; + Toplevel? t = App?.Current; HashSet seen = new (); if (t == null) { - Fail ("Application.Top was null when trying to set focus"); + Fail ("Application.Current was null when trying to set focus"); return this; } @@ -62,7 +62,7 @@ public partial class GuiTestContext } // No, try tab to the next (or first) - EnqueueKeyEvent (Application.NextTabKey); + EnqueueKeyEvent (Terminal.Gui.App.Application.NextTabKey); WaitIteration (); next = t.MostFocused; diff --git a/Tests/TerminalGuiFluentTesting/GuiTestContext.ViewBase.cs b/Tests/TerminalGuiFluentTesting/GuiTestContext.ViewBase.cs index d8848f080..74eafc77e 100644 --- a/Tests/TerminalGuiFluentTesting/GuiTestContext.ViewBase.cs +++ b/Tests/TerminalGuiFluentTesting/GuiTestContext.ViewBase.cs @@ -12,9 +12,9 @@ public partial class GuiTestContext /// public GuiTestContext Add (View v) { - WaitIteration (() => + WaitIteration ((app) => { - Toplevel top = Application.Top ?? throw new ("Top was null so could not add view"); + Toplevel top = app.Current ?? throw new ("Top was null so could not add view"); top.Add (v); top.Layout (); _lastView = v; @@ -28,15 +28,15 @@ public partial class GuiTestContext /// /// The last view added (e.g. with ) or the root/current top. /// - public View LastView => _lastView ?? Application.Top ?? throw new ("Could not determine which view to add to"); + public View LastView => _lastView ?? App.Current ?? throw new ("Could not determine which view to add to"); private T Find (Func evaluator) where T : View { - Toplevel? t = Application.Top; + Toplevel? t = App.Current; if (t == null) { - Fail ("Application.Top was null when attempting to find view"); + Fail ("App.Current was null when attempting to find view"); } T? f = FindRecursive (t!, evaluator); diff --git a/Tests/TerminalGuiFluentTesting/GuiTestContext.cs b/Tests/TerminalGuiFluentTesting/GuiTestContext.cs index de581408f..73960cd0a 100644 --- a/Tests/TerminalGuiFluentTesting/GuiTestContext.cs +++ b/Tests/TerminalGuiFluentTesting/GuiTestContext.cs @@ -1,5 +1,4 @@ -using System.Collections.Concurrent; -using System.Diagnostics; +using System.Diagnostics; using System.Drawing; using System.Text; using Microsoft.Extensions.Logging; @@ -32,10 +31,15 @@ public partial class GuiTestContext : IDisposable private IOutput? _output; private SizeMonitorImpl? _sizeMonitor; private ApplicationImpl? _applicationImpl; + + /// + /// The IApplication instance that was created. + /// + public IApplication? App => _applicationImpl; + private TestDriver _driverType; // ===== Application State Preservation (for restoration) ===== - private IApplication? _originalApplicationInstance; private ILogger? _originalLogger; // ===== Test Configuration ===== @@ -68,7 +72,7 @@ public partial class GuiTestContext : IDisposable _booting.Release (); // After Init, Application.Screen should be set by the driver - if (Application.Screen == Rectangle.Empty) + if (_applicationImpl?.Screen == Rectangle.Empty) { throw new InvalidOperationException ( "Driver bug: Application.Screen is empty after Init. The driver should set the screen size during Init."); @@ -121,11 +125,11 @@ public partial class GuiTestContext : IDisposable Toplevel t = topLevelBuilder (); t.Closed += (s, e) => { Finished = true; }; - Application.Run (t); // This will block, but it's on a background thread now + App?.Run (t); // This will block, but it's on a background thread now t.Dispose (); Logging.Trace ("Application.Run completed"); - Application.Shutdown (); + App?.Shutdown (); _runCancellationTokenSource.Cancel (); } catch (OperationCanceledException) @@ -163,9 +167,7 @@ public partial class GuiTestContext : IDisposable private void InitializeApplication () { - ApplicationImpl.ChangeInstance (_applicationImpl); - - _applicationImpl?.Init (null, GetDriverName ()); + App?.Init (GetDriverName ()); } @@ -175,7 +177,6 @@ public partial class GuiTestContext : IDisposable private void CommonInit (int width, int height, TestDriver driverType, TimeSpan? timeout) { _timeout = timeout ?? TimeSpan.FromSeconds (10); - _originalApplicationInstance = ApplicationImpl.Instance; _originalLogger = Logging.Logger; _logsSb = new (); _driverType = driverType; @@ -207,38 +208,36 @@ public partial class GuiTestContext : IDisposable // Remove frame limit Application.MaximumIterationsPerSecond = ushort.MaxValue; - //// Only set size if explicitly provided (width and height > 0) - //if (width > 0 && height > 0) - //{ - // _output.SetSize (width, height); - //} - IComponentFactory? cf = null; + _output = new FakeOutput (); + + // Only set size if explicitly provided (width and height > 0) + if (width > 0 && height > 0) + { + _output.SetSize (width, height); + } + // TODO: As each drivers' IInput/IOutput implementations are made testable (e.g. // TODO: safely injectable/mocked), we can expand this switch to use them. switch (driverType) { case TestDriver.DotNet: - _output = new FakeOutput (); _sizeMonitor = new (_output); cf = new FakeComponentFactory (_fakeInput, _output, _sizeMonitor); break; case TestDriver.Windows: - _output = new FakeOutput (); _sizeMonitor = new (_output); cf = new FakeComponentFactory (_fakeInput, _output, _sizeMonitor); break; case TestDriver.Unix: - _output = new FakeOutput (); _sizeMonitor = new (_output); cf = new FakeComponentFactory (_fakeInput, _output, _sizeMonitor); break; case TestDriver.Fake: - _output = new FakeOutput (); _sizeMonitor = new (_output); cf = new FakeComponentFactory (_fakeInput, _output, _sizeMonitor); @@ -278,7 +277,7 @@ public partial class GuiTestContext : IDisposable /// /// /// - public GuiTestContext Then (Action doAction) + public GuiTestContext Then (Action doAction) { try { @@ -302,7 +301,7 @@ public partial class GuiTestContext : IDisposable /// /// /// - public GuiTestContext WaitIteration (Action? action = null) + public GuiTestContext WaitIteration (Action? action = null) { // If application has already exited don't wait! if (Finished || _runCancellationTokenSource.Token.IsCancellationRequested || _fakeInput.ExternalCancellationTokenSource!.Token.IsCancellationRequested) @@ -312,31 +311,34 @@ public partial class GuiTestContext : IDisposable return this; } - if (Thread.CurrentThread.ManagedThreadId == Application.MainThreadId) + if (Thread.CurrentThread.ManagedThreadId == _applicationImpl?.MainThreadId) { throw new NotSupportedException ("Cannot WaitIteration during Invoke"); } Logging.Trace ($"WaitIteration started"); - action ??= () => { }; + if (action is null) + { + action = (app) => { }; + } CancellationTokenSource ctsActionCompleted = new (); - Application.Invoke (() => - { - try - { - action (); + App?.Invoke (app => + { + try + { + action (app); - //Logging.Trace ("Action completed"); - ctsActionCompleted.Cancel (); - } - catch (Exception e) - { - Logging.Warning ($"Action failed with exception: {e}"); - _backgroundException = e; - _fakeInput.ExternalCancellationTokenSource?.Cancel (); - } - }); + //Logging.Trace ("Action completed"); + ctsActionCompleted.Cancel (); + } + catch (Exception e) + { + Logging.Warning ($"Action failed with exception: {e}"); + _backgroundException = e; + _fakeInput.ExternalCancellationTokenSource?.Cancel (); + } + }); // Blocks until either the token or the hardStopToken is cancelled. // With linked tokens, we only need to wait on _runCancellationTokenSource and ctsLocal @@ -384,15 +386,27 @@ public partial class GuiTestContext : IDisposable /// new Width for the console. /// new Height for the console. /// - public GuiTestContext ResizeConsole (int width, int height) { return WaitIteration (() => { Application.Driver!.SetScreenSize (width, height); }); } + public GuiTestContext ResizeConsole (int width, int height) { return WaitIteration ((app) => { app.Driver!.SetScreenSize (width, height); }); } public GuiTestContext ScreenShot (string title, TextWriter? writer) { //Logging.Trace ($"{title}"); - return WaitIteration (() => + return WaitIteration ((app) => { writer?.WriteLine (title + ":"); - var text = Application.ToString (); + var text = app.Driver?.ToString (); + + writer?.WriteLine (text); + }); + } + + public GuiTestContext AnsiScreenShot (string title, TextWriter? writer) + { + //Logging.Trace ($"{title}"); + return WaitIteration ((app) => + { + writer?.WriteLine (title + ":"); + var text = app.Driver?.ToAnsi (); writer?.WriteLine (text); }); @@ -412,7 +426,7 @@ public partial class GuiTestContext : IDisposable { try { - Application.Shutdown (); + App?.Shutdown (); } catch { @@ -425,7 +439,7 @@ public partial class GuiTestContext : IDisposable return this; } - WaitIteration (() => { Application.RequestStop (); }); + WaitIteration ((app) => { app.RequestStop (); }); // Wait for the application to stop, but give it a 1-second timeout const int WAIT_TIMEOUT_MS = 1000; @@ -440,8 +454,8 @@ public partial class GuiTestContext : IDisposable // If this doesn't work there will be test failures as the main loop continues to run during next test. try { - Application.RequestStop (); - Application.Shutdown (); + App?.RequestStop (); + App?.Shutdown (); } catch (Exception ex) { @@ -516,9 +530,8 @@ public partial class GuiTestContext : IDisposable Logging.Trace ("CleanupApplication"); _fakeInput.ExternalCancellationTokenSource = null; - Application.ResetState (true); - ApplicationImpl.ChangeInstance (_originalApplicationInstance); - Logging.Logger = _originalLogger; + App?.ResetState (true); + Logging.Logger = _originalLogger!; Finished = true; Application.MaximumIterationsPerSecond = Application.DefaultMaximumIterationsPerSecond; diff --git a/Tests/UnitTests/Application/Application.NavigationTests.cs b/Tests/UnitTests/Application/Application.NavigationTests.cs index 93ad1ae91..21cafa132 100644 --- a/Tests/UnitTests/Application/Application.NavigationTests.cs +++ b/Tests/UnitTests/Application/Application.NavigationTests.cs @@ -61,9 +61,7 @@ public class ApplicationNavigationTests (ITestOutputHelper output) { var raised = false; - Application.Navigation = new (); - - Application.Navigation.FocusedChanged += ApplicationNavigationOnFocusedChanged; + Application.Navigation!.FocusedChanged += ApplicationNavigationOnFocusedChanged; Application.Navigation.SetFocused (new () { CanFocus = true, HasFocus = true }); @@ -74,8 +72,6 @@ public class ApplicationNavigationTests (ITestOutputHelper output) Application.Navigation.FocusedChanged -= ApplicationNavigationOnFocusedChanged; - Application.Navigation = null; - return; void ApplicationNavigationOnFocusedChanged (object sender, EventArgs e) { raised = true; } @@ -84,12 +80,13 @@ public class ApplicationNavigationTests (ITestOutputHelper output) [Fact] public void GetFocused_Returns_Focused_View () { - Application.Navigation = new (); + IApplication app = Application.Create (); - Application.Top = new () + app.Current = new () { Id = "top", - CanFocus = true + CanFocus = true, + App = app }; var subView1 = new View @@ -103,30 +100,28 @@ public class ApplicationNavigationTests (ITestOutputHelper output) Id = "subView2", CanFocus = true }; - Application.Top.Add (subView1, subView2); - Assert.False (Application.Top.HasFocus); - Application.Top.SetFocus (); + app.Current?.Add (subView1, subView2); + Assert.False (app.Current?.HasFocus); + + app.Current?.SetFocus (); Assert.True (subView1.HasFocus); - Assert.Equal (subView1, Application.Navigation.GetFocused ()); + Assert.Equal (subView1, app.Navigation?.GetFocused ()); - Application.Navigation.AdvanceFocus (NavigationDirection.Forward, null); - Assert.Equal (subView2, Application.Navigation.GetFocused ()); - - Application.Top.Dispose (); - Application.Top = null; - Application.Navigation = null; + app.Navigation?.AdvanceFocus (NavigationDirection.Forward, null); + Assert.Equal (subView2, app.Navigation?.GetFocused ()); } [Fact] public void GetFocused_Returns_Null_If_No_Focused_View () { - Application.Navigation = new (); + IApplication app = Application.Create (); - Application.Top = new () + app.Current = new () { Id = "top", - CanFocus = true + CanFocus = true, + App = app }; var subView1 = new View @@ -135,24 +130,21 @@ public class ApplicationNavigationTests (ITestOutputHelper output) CanFocus = true }; - Application.Top.Add (subView1); - Assert.False (Application.Top.HasFocus); + app!.Current.Add (subView1); + Assert.False (app.Current.HasFocus); - Application.Top.SetFocus (); + app.Current.SetFocus (); Assert.True (subView1.HasFocus); - Assert.Equal (subView1, Application.Navigation.GetFocused ()); + Assert.Equal (subView1, app.Navigation!.GetFocused ()); subView1.HasFocus = false; Assert.False (subView1.HasFocus); - Assert.True (Application.Top.HasFocus); - Assert.Equal (Application.Top, Application.Navigation.GetFocused ()); + Assert.True (app.Current.HasFocus); + Assert.Equal (app.Current, app.Navigation.GetFocused ()); - Application.Top.HasFocus = false; - Assert.False (Application.Top.HasFocus); - Assert.Null (Application.Navigation.GetFocused ()); + app.Current.HasFocus = false; + Assert.False (app.Current.HasFocus); + Assert.Null (app.Navigation.GetFocused ()); - Application.Top.Dispose (); - Application.Top = null; - Application.Navigation = null; } } diff --git a/Tests/UnitTests/Application/ApplicationImplBeginEndTests.cs b/Tests/UnitTests/Application/ApplicationImplBeginEndTests.cs new file mode 100644 index 000000000..da9faad9d --- /dev/null +++ b/Tests/UnitTests/Application/ApplicationImplBeginEndTests.cs @@ -0,0 +1,505 @@ +#nullable enable +using Xunit.Abstractions; + +namespace UnitTests.ApplicationTests; + +/// +/// Comprehensive tests for ApplicationImpl.Begin/End logic that manages Current and SessionStack. +/// These tests ensure the fragile state management logic is robust and catches regressions. +/// Tests work directly with ApplicationImpl instances to avoid global Application state issues. +/// +public class ApplicationImplBeginEndTests +{ + private readonly ITestOutputHelper _output; + + public ApplicationImplBeginEndTests (ITestOutputHelper output) { _output = output; } + + private IApplication NewApplicationImpl () + { + IApplication app = Application.Create (); + + return app; + } + + [Fact] + public void Begin_WithNullToplevel_ThrowsArgumentNullException () + { + IApplication app = NewApplicationImpl (); + + try + { + Assert.Throws (() => app.Begin (null!)); + } + finally + { + app.Shutdown (); + } + } + + [Fact] + public void Begin_SetsCurrent_WhenCurrentIsNull () + { + IApplication app = NewApplicationImpl (); + Toplevel? toplevel = null; + + try + { + toplevel = new (); + Assert.Null (app.Current); + + app.Begin (toplevel); + + Assert.NotNull (app.Current); + Assert.Same (toplevel, app.Current); + Assert.Single (app.SessionStack); + } + finally + { + toplevel?.Dispose (); + app.Shutdown (); + } + } + + [Fact] + public void Begin_PushesToSessionStack () + { + IApplication app = NewApplicationImpl (); + Toplevel? toplevel1 = null; + Toplevel? toplevel2 = null; + + try + { + toplevel1 = new() { Id = "1" }; + toplevel2 = new() { Id = "2" }; + + app.Begin (toplevel1); + Assert.Single (app.SessionStack); + Assert.Same (toplevel1, app.Current); + + app.Begin (toplevel2); + Assert.Equal (2, app.SessionStack.Count); + Assert.Same (toplevel2, app.Current); + } + finally + { + toplevel1?.Dispose (); + toplevel2?.Dispose (); + app.Shutdown (); + } + } + + [Fact] + public void Begin_SetsUniqueToplevelId_WhenIdIsEmpty () + { + IApplication app = NewApplicationImpl (); + Toplevel? toplevel1 = null; + Toplevel? toplevel2 = null; + Toplevel? toplevel3 = null; + + try + { + toplevel1 = new (); + toplevel2 = new (); + toplevel3 = new (); + + Assert.Empty (toplevel1.Id); + Assert.Empty (toplevel2.Id); + Assert.Empty (toplevel3.Id); + + app.Begin (toplevel1); + app.Begin (toplevel2); + app.Begin (toplevel3); + + Assert.NotEmpty (toplevel1.Id); + Assert.NotEmpty (toplevel2.Id); + Assert.NotEmpty (toplevel3.Id); + + // IDs should be unique + Assert.NotEqual (toplevel1.Id, toplevel2.Id); + Assert.NotEqual (toplevel2.Id, toplevel3.Id); + Assert.NotEqual (toplevel1.Id, toplevel3.Id); + } + finally + { + toplevel1?.Dispose (); + toplevel2?.Dispose (); + toplevel3?.Dispose (); + app.Shutdown (); + } + } + + [Fact] + public void End_WithNullSessionToken_ThrowsArgumentNullException () + { + IApplication app = NewApplicationImpl (); + + try + { + Assert.Throws (() => app.End (null!)); + } + finally + { + app.Shutdown (); + } + } + + [Fact] + public void End_PopsSessionStack () + { + IApplication app = NewApplicationImpl (); + Toplevel? toplevel1 = null; + Toplevel? toplevel2 = null; + + try + { + toplevel1 = new() { Id = "1" }; + toplevel2 = new() { Id = "2" }; + + SessionToken token1 = app.Begin (toplevel1); + SessionToken token2 = app.Begin (toplevel2); + + Assert.Equal (2, app.SessionStack.Count); + + app.End (token2); + + Assert.Single (app.SessionStack); + Assert.Same (toplevel1, app.Current); + + app.End (token1); + + Assert.Empty (app.SessionStack); + } + finally + { + toplevel1?.Dispose (); + toplevel2?.Dispose (); + app.Shutdown (); + } + } + + [Fact] + public void End_ThrowsArgumentException_WhenNotBalanced () + { + IApplication app = NewApplicationImpl (); + Toplevel? toplevel1 = null; + Toplevel? toplevel2 = null; + + try + { + toplevel1 = new() { Id = "1" }; + toplevel2 = new() { Id = "2" }; + + SessionToken token1 = app.Begin (toplevel1); + SessionToken token2 = app.Begin (toplevel2); + + // Trying to end token1 when token2 is on top should throw + // NOTE: This throws but has the side effect of popping token2 from the stack + Assert.Throws (() => app.End (token1)); + + // Don't try to clean up with more End calls - the state is now inconsistent + // Let Shutdown/ResetState handle cleanup + } + finally + { + // Dispose toplevels BEFORE Shutdown to satisfy DEBUG_IDISPOSABLE assertions + toplevel1?.Dispose (); + toplevel2?.Dispose (); + + // Shutdown will call ResetState which clears any remaining state + app.Shutdown (); + } + } + + [Fact] + public void End_RestoresCurrentToPreviousToplevel () + { + IApplication app = NewApplicationImpl (); + Toplevel? toplevel1 = null; + Toplevel? toplevel2 = null; + Toplevel? toplevel3 = null; + + try + { + toplevel1 = new() { Id = "1" }; + toplevel2 = new() { Id = "2" }; + toplevel3 = new() { Id = "3" }; + + SessionToken token1 = app.Begin (toplevel1); + SessionToken token2 = app.Begin (toplevel2); + SessionToken token3 = app.Begin (toplevel3); + + Assert.Same (toplevel3, app.Current); + + app.End (token3); + Assert.Same (toplevel2, app.Current); + + app.End (token2); + Assert.Same (toplevel1, app.Current); + + app.End (token1); + } + finally + { + toplevel1?.Dispose (); + toplevel2?.Dispose (); + toplevel3?.Dispose (); + app.Shutdown (); + } + } + + [Fact] + public void MultipleBeginEnd_MaintainsStackIntegrity () + { + IApplication app = NewApplicationImpl (); + List toplevels = new (); + List tokens = new (); + + try + { + // Begin multiple toplevels + for (var i = 0; i < 5; i++) + { + var toplevel = new Toplevel { Id = $"toplevel-{i}" }; + toplevels.Add (toplevel); + tokens.Add (app.Begin (toplevel)); + } + + Assert.Equal (5, app.SessionStack.Count); + Assert.Same (toplevels [4], app.Current); + + // End them in reverse order (LIFO) + for (var i = 4; i >= 0; i--) + { + app.End (tokens [i]); + + if (i > 0) + { + Assert.Equal (i, app.SessionStack.Count); + Assert.Same (toplevels [i - 1], app.Current); + } + else + { + Assert.Empty (app.SessionStack); + } + } + } + finally + { + foreach (Toplevel toplevel in toplevels) + { + toplevel.Dispose (); + } + + app.Shutdown (); + } + } + + [Fact] + public void End_UpdatesCachedSessionTokenToplevel () + { + IApplication app = NewApplicationImpl (); + Toplevel? toplevel = null; + + try + { + toplevel = new (); + + SessionToken token = app.Begin (toplevel); + Assert.Null (app.CachedSessionTokenToplevel); + + app.End (token); + + Assert.Same (toplevel, app.CachedSessionTokenToplevel); + } + finally + { + toplevel?.Dispose (); + app.Shutdown (); + } + } + + [Fact] + public void End_NullsSessionTokenToplevel () + { + IApplication app = NewApplicationImpl (); + Toplevel? toplevel = null; + + try + { + toplevel = new (); + + SessionToken token = app.Begin (toplevel); + Assert.Same (toplevel, token.Toplevel); + + app.End (token); + + Assert.Null (token.Toplevel); + } + finally + { + toplevel?.Dispose (); + app.Shutdown (); + } + } + + [Fact] + public void ResetState_ClearsSessionStack () + { + IApplication app = NewApplicationImpl (); + Toplevel? toplevel1 = null; + Toplevel? toplevel2 = null; + + try + { + toplevel1 = new() { Id = "1" }; + toplevel2 = new() { Id = "2" }; + + app.Begin (toplevel1); + app.Begin (toplevel2); + + Assert.Equal (2, app.SessionStack.Count); + Assert.NotNull (app.Current); + } + finally + { + // Dispose toplevels BEFORE Shutdown to satisfy DEBUG_IDISPOSABLE assertions + toplevel1?.Dispose (); + toplevel2?.Dispose (); + + // Shutdown calls ResetState, which will clear SessionStack and set Current to null + app.Shutdown (); + + // Verify cleanup happened + Assert.Empty (app.SessionStack); + Assert.Null (app.Current); + Assert.Null (app.CachedSessionTokenToplevel); + } + } + + [Fact] + public void ResetState_StopsAllRunningToplevels () + { + IApplication app = NewApplicationImpl (); + Toplevel? toplevel1 = null; + Toplevel? toplevel2 = null; + + try + { + toplevel1 = new() { Id = "1", Running = true }; + toplevel2 = new() { Id = "2", Running = true }; + + app.Begin (toplevel1); + app.Begin (toplevel2); + + Assert.True (toplevel1.Running); + Assert.True (toplevel2.Running); + } + finally + { + // Dispose toplevels BEFORE Shutdown to satisfy DEBUG_IDISPOSABLE assertions + toplevel1?.Dispose (); + toplevel2?.Dispose (); + + // Shutdown calls ResetState, which will stop all running toplevels + app.Shutdown (); + + // Verify toplevels were stopped + Assert.False (toplevel1!.Running); + Assert.False (toplevel2!.Running); + } + } + + [Fact] + public void Begin_ActivatesNewToplevel_WhenCurrentExists () + { + IApplication app = NewApplicationImpl (); + Toplevel? toplevel1 = null; + Toplevel? toplevel2 = null; + + try + { + toplevel1 = new() { Id = "1" }; + toplevel2 = new() { Id = "2" }; + + var toplevel1Deactivated = false; + var toplevel2Activated = false; + + toplevel1.Deactivate += (s, e) => toplevel1Deactivated = true; + toplevel2.Activate += (s, e) => toplevel2Activated = true; + + app.Begin (toplevel1); + app.Begin (toplevel2); + + Assert.True (toplevel1Deactivated); + Assert.True (toplevel2Activated); + Assert.Same (toplevel2, app.Current); + } + finally + { + toplevel1?.Dispose (); + toplevel2?.Dispose (); + app.Shutdown (); + } + } + + [Fact] + public void Begin_DoesNotDuplicateToplevel_WhenIdAlreadyExists () + { + IApplication app = NewApplicationImpl (); + Toplevel? toplevel = null; + + try + { + toplevel = new() { Id = "test-id" }; + + app.Begin (toplevel); + Assert.Single (app.SessionStack); + + // Calling Begin again with same toplevel should not duplicate + app.Begin (toplevel); + Assert.Single (app.SessionStack); + } + finally + { + toplevel?.Dispose (); + app.Shutdown (); + } + } + + [Fact] + public void SessionStack_ContainsAllBegunToplevels () + { + IApplication app = NewApplicationImpl (); + List toplevels = new (); + + try + { + for (var i = 0; i < 10; i++) + { + var toplevel = new Toplevel { Id = $"toplevel-{i}" }; + toplevels.Add (toplevel); + app.Begin (toplevel); + } + + // All toplevels should be in the stack + Assert.Equal (10, app.SessionStack.Count); + + // Verify stack contains all toplevels + List stackList = app.SessionStack.ToList (); + + foreach (Toplevel toplevel in toplevels) + { + Assert.Contains (toplevel, stackList); + } + } + finally + { + foreach (Toplevel toplevel in toplevels) + { + toplevel.Dispose (); + } + + app.Shutdown (); + } + } +} diff --git a/Tests/UnitTests/Application/ApplicationImplTests.cs b/Tests/UnitTests/Application/ApplicationImplTests.cs index d31a254d3..faa4cf7a8 100644 --- a/Tests/UnitTests/Application/ApplicationImplTests.cs +++ b/Tests/UnitTests/Application/ApplicationImplTests.cs @@ -1,7 +1,6 @@ #nullable enable using System.Collections.Concurrent; using Moq; -using TerminalGuiFluentTesting; namespace UnitTests.ApplicationTests; @@ -10,7 +9,7 @@ public class ApplicationImplTests /// /// Crates a new ApplicationImpl instance for testing. The input, output, and size monitor components are mocked. /// - private ApplicationImpl NewMockedApplicationImpl () + private IApplication? NewMockedApplicationImpl () { Mock netInput = new (); SetupRunInputMockMethodToBlock (netInput); @@ -21,132 +20,30 @@ public class ApplicationImplTests Mock consoleOutput = new (); var size = new Size (80, 25); + consoleOutput.Setup (o => o.SetSize (It.IsAny (), It.IsAny ())) - .Callback ((w, h) => size = new Size (w, h)); + .Callback ((w, h) => size = new (w, h)); consoleOutput.Setup (o => o.GetSize ()).Returns (() => size); m.Setup (f => f.CreateOutput ()).Returns (consoleOutput.Object); m.Setup (f => f.CreateSizeMonitor (It.IsAny (), It.IsAny ())).Returns (Mock.Of ()); - return new (m.Object); + return new ApplicationImpl (m.Object); } [Fact] public void Init_CreatesKeybindings () { - IApplication orig = ApplicationImpl.Instance; + IApplication? app = NewMockedApplicationImpl (); - ApplicationImpl v2 = NewMockedApplicationImpl (); - ApplicationImpl.ChangeInstance (v2); + app?.Keyboard.KeyBindings.Clear (); - Application.KeyBindings.Clear (); + Assert.Empty (app?.Keyboard?.KeyBindings.GetBindings ()!); - Assert.Empty (Application.KeyBindings.GetBindings ()); + app?.Init ("fake"); - v2.Init (null, "fake"); + Assert.NotEmpty (app?.Keyboard?.KeyBindings.GetBindings ()!); - Assert.NotEmpty (Application.KeyBindings.GetBindings ()); - - v2.Shutdown (); - - ApplicationImpl.ChangeInstance (orig); - } - - /* - [Fact] - public void Init_ExplicitlyRequestWin () - { - var orig = ApplicationImpl.Instance; - - Assert.Null (Application.Driver); - var netInput = new Mock (MockBehavior.Strict); - var netOutput = new Mock (MockBehavior.Strict); - var winInput = new Mock (MockBehavior.Strict); - var winOutput = new Mock (MockBehavior.Strict); - - winInput.Setup (i => i.Initialize (It.IsAny> ())) - .Verifiable (Times.Once); - SetupRunInputMockMethodToBlock (winInput); - winInput.Setup (i => i.Dispose ()) - .Verifiable (Times.Once); - winOutput.Setup (i => i.Dispose ()) - .Verifiable (Times.Once); - - var v2 = new ApplicationV2 ( - () => netInput.Object, - () => netOutput.Object, - () => winInput.Object, - () => winOutput.Object); - ApplicationImpl.ChangeInstance (v2); - - Assert.Null (Application.Driver); - v2.Init (null, "v2win"); - Assert.NotNull (Application.Driver); - - var type = Application.Driver.GetType (); - Assert.True (type.IsGenericType); - Assert.True (type.GetGenericTypeDefinition () == typeof (ConsoleDriverFacade<>)); - v2.Shutdown (); - - Assert.Null (Application.Driver); - - winInput.VerifyAll (); - - ApplicationImpl.ChangeInstance (orig); - } - - [Fact] - public void Init_ExplicitlyRequestNet () - { - var orig = ApplicationImpl.Instance; - - var netInput = new Mock (MockBehavior.Strict); - var netOutput = new Mock (MockBehavior.Strict); - var winInput = new Mock (MockBehavior.Strict); - var winOutput = new Mock (MockBehavior.Strict); - - netInput.Setup (i => i.Initialize (It.IsAny> ())) - .Verifiable (Times.Once); - SetupRunInputMockMethodToBlock (netInput); - netInput.Setup (i => i.Dispose ()) - .Verifiable (Times.Once); - netOutput.Setup (i => i.Dispose ()) - .Verifiable (Times.Once); - var v2 = new ApplicationV2 ( - () => netInput.Object, - () => netOutput.Object, - () => winInput.Object, - () => winOutput.Object); - ApplicationImpl.ChangeInstance (v2); - - Assert.Null (Application.Driver); - v2.Init (null, "v2net"); - Assert.NotNull (Application.Driver); - - var type = Application.Driver.GetType (); - Assert.True (type.IsGenericType); - Assert.True (type.GetGenericTypeDefinition () == typeof (ConsoleDriverFacade<>)); - v2.Shutdown (); - - Assert.Null (Application.Driver); - - netInput.VerifyAll (); - - ApplicationImpl.ChangeInstance (orig); - } -*/ - private void SetupRunInputMockMethodToBlock (Mock> winInput) - { - winInput.Setup (r => r.Run (It.IsAny ())) - .Callback (token => - { - // Simulate an infinite loop that checks for cancellation - while (!token.IsCancellationRequested) - { - // Perform the action that should repeat in the loop - // This could be some mock behavior or just an empty loop depending on the context - } - }) - .Verifiable (Times.Once); + app?.Shutdown (); } private void SetupRunInputMockMethodToBlock (Mock netInput) @@ -167,125 +64,105 @@ public class ApplicationImplTests [Fact] public void NoInitThrowOnRun () { - IApplication orig = ApplicationImpl.Instance; - - Assert.Null (Application.Driver); - ApplicationImpl app = NewMockedApplicationImpl (); - ApplicationImpl.ChangeInstance (app); - - var ex = Assert.Throws (() => app.Run (new Window ())); + IApplication? app = NewMockedApplicationImpl (); + var ex = Assert.Throws (() => app?.Run (new Window ())); Assert.Equal ("Run cannot be accessed before Initialization", ex.Message); - app.Shutdown (); - - ApplicationImpl.ChangeInstance (orig); + app?.Shutdown (); } [Fact] public void InitRunShutdown_Top_Set_To_Null_After_Shutdown () { - IApplication orig = ApplicationImpl.Instance; + IApplication? app = NewMockedApplicationImpl (); - ApplicationImpl v2 = NewMockedApplicationImpl (); - ApplicationImpl.ChangeInstance (v2); + app?.Init ("fake"); - v2.Init (null, "fake"); + object? timeoutToken = app?.AddTimeout ( + TimeSpan.FromMilliseconds (150), + () => + { + if (app.Current is { }) + { + app.RequestStop (); - object timeoutToken = v2.AddTimeout ( - TimeSpan.FromMilliseconds (150), - () => - { - if (Application.Top != null) - { - Application.RequestStop (); + return false; + } - return false; - } - - return false; - } - ); - Assert.Null (Application.Top); + return false; + } + ); + Assert.Null (app?.Current); // Blocks until the timeout call is hit - v2.Run (new Window ()); + app?.Run (new Window ()); // We returned false above, so we should not have to remove the timeout - Assert.False (v2.RemoveTimeout (timeoutToken)); + Assert.False (app?.RemoveTimeout (timeoutToken!)); - Assert.NotNull (Application.Top); - Application.Top?.Dispose (); - v2.Shutdown (); - Assert.Null (Application.Top); - - ApplicationImpl.ChangeInstance (orig); + Assert.NotNull (app?.Current); + app.Current?.Dispose (); + app.Shutdown (); + Assert.Null (app.Current); } [Fact] public void InitRunShutdown_Running_Set_To_False () { - IApplication orig = ApplicationImpl.Instance; + IApplication app = NewMockedApplicationImpl ()!; - ApplicationImpl v2 = NewMockedApplicationImpl (); - ApplicationImpl.ChangeInstance (v2); - - v2.Init (null, "fake"); + app.Init ("fake"); Toplevel top = new Window { Title = "InitRunShutdown_Running_Set_To_False" }; - object timeoutToken = v2.AddTimeout ( - TimeSpan.FromMilliseconds (150), - () => - { - Assert.True (top!.Running); + object timeoutToken = app.AddTimeout ( + TimeSpan.FromMilliseconds (150), + () => + { + Assert.True (top!.Running); - if (Application.Top != null) - { - Application.RequestStop (); + if (app.Current != null) + { + app.RequestStop (); - return false; - } + return false; + } - return false; - } - ); + return false; + } + ); Assert.False (top!.Running); // Blocks until the timeout call is hit - v2.Run (top); + app.Run (top); // We returned false above, so we should not have to remove the timeout - Assert.False (v2.RemoveTimeout (timeoutToken)); + Assert.False (app.RemoveTimeout (timeoutToken)); Assert.False (top!.Running); // BUGBUG: Shutdown sets Top to null, not End. - //Assert.Null (Application.Top); - Application.Top?.Dispose (); - v2.Shutdown (); - - ApplicationImpl.ChangeInstance (orig); + //Assert.Null (Application.Current); + app.Current?.Dispose (); + app.Shutdown (); } - [Fact] public void InitRunShutdown_StopAfterFirstIteration_Stops () { - IApplication orig = ApplicationImpl.Instance; + IApplication app = NewMockedApplicationImpl ()!; - ApplicationImpl v2 = NewMockedApplicationImpl (); - ApplicationImpl.ChangeInstance (v2); + Assert.Null (app.Current); + Assert.Null (app.Driver); - Assert.Null (Application.Top); - Assert.Null (Application.Driver); - - v2.Init (null, "fake"); + app.Init ("fake"); Toplevel top = new Window (); + app.Current = top; var closedCount = 0; @@ -297,45 +174,40 @@ public class ApplicationImplTests top.Unloaded += (_, a) => { unloadedCount++; }; - object timeoutToken = v2.AddTimeout ( - TimeSpan.FromMilliseconds (150), - () => - { - Assert.Fail (@"Didn't stop after first iteration."); - return false; - } - ); + object timeoutToken = app.AddTimeout ( + TimeSpan.FromMilliseconds (150), + () => + { + Assert.Fail (@"Didn't stop after first iteration."); + + return false; + } + ); Assert.Equal (0, closedCount); Assert.Equal (0, unloadedCount); - v2.StopAfterFirstIteration = true; - v2.Run (top); + app.StopAfterFirstIteration = true; + app.Run (top); Assert.Equal (1, closedCount); Assert.Equal (1, unloadedCount); - Application.Top?.Dispose (); - v2.Shutdown (); + app.Current?.Dispose (); + app.Shutdown (); Assert.Equal (1, closedCount); Assert.Equal (1, unloadedCount); - - ApplicationImpl.ChangeInstance (orig); } - [Fact] public void InitRunShutdown_End_Is_Called () { - IApplication orig = ApplicationImpl.Instance; + IApplication app = NewMockedApplicationImpl ()!; - ApplicationImpl v2 = NewMockedApplicationImpl (); - ApplicationImpl.ChangeInstance (v2); + Assert.Null (app.Current); + Assert.Null (app.Driver); - Assert.Null (Application.Top); - Assert.Null (Application.Driver); - - v2.Init (null, "fake"); + app.Init ("fake"); Toplevel top = new Window (); @@ -350,125 +222,110 @@ public class ApplicationImplTests top.Unloaded += (_, a) => { unloadedCount++; }; - object timeoutToken = v2.AddTimeout ( - TimeSpan.FromMilliseconds (150), - () => - { - Assert.True (top!.Running); + object timeoutToken = app.AddTimeout ( + TimeSpan.FromMilliseconds (150), + () => + { + Assert.True (top!.Running); - if (Application.Top != null) - { - Application.RequestStop (); + if (app.Current != null) + { + app.RequestStop (); - return false; - } + return false; + } - return false; - } - ); + return false; + } + ); Assert.Equal (0, closedCount); Assert.Equal (0, unloadedCount); // Blocks until the timeout call is hit - v2.Run (top); + app.Run (top); Assert.Equal (1, closedCount); Assert.Equal (1, unloadedCount); // We returned false above, so we should not have to remove the timeout - Assert.False (v2.RemoveTimeout (timeoutToken)); + Assert.False (app.RemoveTimeout (timeoutToken)); - Application.Top?.Dispose (); - v2.Shutdown (); + app.Current?.Dispose (); + app.Shutdown (); Assert.Equal (1, closedCount); Assert.Equal (1, unloadedCount); - - ApplicationImpl.ChangeInstance (orig); } [Fact] public void InitRunShutdown_QuitKey_Quits () { - IApplication orig = ApplicationImpl.Instance; + IApplication app = NewMockedApplicationImpl ()!; - ApplicationImpl v2 = NewMockedApplicationImpl (); - ApplicationImpl.ChangeInstance (v2); - - v2.Init (null, "fake"); + app.Init ("fake"); Toplevel top = new Window { Title = "InitRunShutdown_QuitKey_Quits" }; - object timeoutToken = v2.AddTimeout ( - TimeSpan.FromMilliseconds (150), - () => - { - Assert.True (top!.Running); + object timeoutToken = app.AddTimeout ( + TimeSpan.FromMilliseconds (150), + () => + { + Assert.True (top!.Running); - if (Application.Top != null) - { - Application.RaiseKeyDownEvent (Application.QuitKey); - } + if (app.Current != null) + { + app.Keyboard.RaiseKeyDownEvent (app.Keyboard.QuitKey); + } - return false; - } - ); + return false; + } + ); Assert.False (top!.Running); // Blocks until the timeout call is hit - v2.Run (top); + app.Run (top); // We returned false above, so we should not have to remove the timeout - Assert.False (v2.RemoveTimeout (timeoutToken)); + Assert.False (app.RemoveTimeout (timeoutToken)); Assert.False (top!.Running); - Assert.NotNull (Application.Top); + Assert.NotNull (app.Current); top.Dispose (); - v2.Shutdown (); - Assert.Null (Application.Top); - - ApplicationImpl.ChangeInstance (orig); + app.Shutdown (); + Assert.Null (app.Current); } [Fact] public void InitRunShutdown_Generic_IdleForExit () { - IApplication orig = ApplicationImpl.Instance; + IApplication app = NewMockedApplicationImpl ()!; - ApplicationImpl v2 = NewMockedApplicationImpl (); - ApplicationImpl.ChangeInstance (v2); + app.Init ("fake"); - v2.Init (null, "fake"); - - v2.AddTimeout (TimeSpan.Zero, IdleExit); - Assert.Null (Application.Top); + app.AddTimeout (TimeSpan.Zero, () => IdleExit (app)); + Assert.Null (app.Current); // Blocks until the timeout call is hit - v2.Run (); + app.Run (); - Assert.NotNull (Application.Top); - Application.Top?.Dispose (); - v2.Shutdown (); - Assert.Null (Application.Top); - - ApplicationImpl.ChangeInstance (orig); + Assert.NotNull (app.Current); + app.Current?.Dispose (); + app.Shutdown (); + Assert.Null (app.Current); } [Fact] public void Shutdown_Closing_Closed_Raised () { - IApplication orig = ApplicationImpl.Instance; + IApplication app = NewMockedApplicationImpl ()!; - ApplicationImpl v2 = NewMockedApplicationImpl (); - ApplicationImpl.ChangeInstance (v2); - - v2.Init (null, "fake"); + app.Init ("fake"); var closing = 0; var closed = 0; @@ -494,69 +351,37 @@ public class ApplicationImplTests Assert.Same (t, a.Toplevel); }; - v2.AddTimeout (TimeSpan.Zero, IdleExit); + app.AddTimeout (TimeSpan.Zero, () => IdleExit (app)); // Blocks until the timeout call is hit - v2.Run (t); + app.Run (t); - Application.Top?.Dispose (); - v2.Shutdown (); - - ApplicationImpl.ChangeInstance (orig); + app.Current?.Dispose (); + app.Shutdown (); Assert.Equal (2, closing); Assert.Equal (1, closed); } - private bool IdleExit () + private bool IdleExit (IApplication app) { - if (Application.Top != null) + if (app.Current != null) { - Application.RequestStop (); + app.RequestStop (); return true; } return true; } - /* - [Fact] - public void Shutdown_Called_Repeatedly_DoNotDuplicateDisposeOutput () - { - var orig = ApplicationImpl.Instance; - - var netInput = new Mock (); - SetupRunInputMockMethodToBlock (netInput); - Mock? outputMock = null; - - - var v2 = new ApplicationV2 ( - () => netInput.Object, - () => (outputMock = new Mock ()).Object, - Mock.Of, - Mock.Of); - ApplicationImpl.ChangeInstance (v2); - - v2.Init (null, "v2net"); - - - v2.Shutdown (); - outputMock!.Verify (o => o.Dispose (), Times.Once); - - ApplicationImpl.ChangeInstance (orig); - } - */ [Fact] public void Open_Calls_ContinueWith_On_UIThread () { - IApplication orig = ApplicationImpl.Instance; + IApplication app = NewMockedApplicationImpl ()!; - ApplicationImpl v2 = NewMockedApplicationImpl (); - ApplicationImpl.ChangeInstance (v2); - - v2.Init (null, "fake"); + app.Init ("fake"); var b = new Button (); var result = false; @@ -569,30 +394,30 @@ public class ApplicationImplTests (t, _) => { // no longer loading - Application.Invoke (() => - { - result = true; - Application.RequestStop (); - }); + app.Invoke (() => + { + result = true; + app.RequestStop (); + }); }, TaskScheduler.FromCurrentSynchronizationContext ()); }; - v2.AddTimeout ( - TimeSpan.FromMilliseconds (150), - () => - { - // Run asynchronous logic inside Task.Run - if (Application.Top != null) - { - b.NewKeyDownEvent (Key.Enter); - b.NewKeyUpEvent (Key.Enter); - } + app.AddTimeout ( + TimeSpan.FromMilliseconds (150), + () => + { + // Run asynchronous logic inside Task.Run + if (app.Current != null) + { + b.NewKeyDownEvent (Key.Enter); + b.NewKeyUpEvent (Key.Enter); + } - return false; - }); + return false; + }); - Assert.Null (Application.Top); + Assert.Null (app.Current); var w = new Window { @@ -601,14 +426,12 @@ public class ApplicationImplTests w.Add (b); // Blocks until the timeout call is hit - v2.Run (w); + app.Run (w); - Assert.NotNull (Application.Top); - Application.Top?.Dispose (); - v2.Shutdown (); - Assert.Null (Application.Top); - - ApplicationImpl.ChangeInstance (orig); + Assert.NotNull (app.Current); + app.Current?.Dispose (); + app.Shutdown (); + Assert.Null (app.Current); Assert.True (result); } @@ -617,47 +440,36 @@ public class ApplicationImplTests public void ApplicationImpl_UsesInstanceFields_NotStaticReferences () { // This test verifies that ApplicationImpl uses instance fields instead of static Application references - IApplication orig = ApplicationImpl.Instance; - - ApplicationImpl v2 = NewMockedApplicationImpl (); - ApplicationImpl.ChangeInstance (v2); + IApplication v2 = NewMockedApplicationImpl ()!; // Before Init, all fields should be null/default Assert.Null (v2.Driver); Assert.False (v2.Initialized); - Assert.Null (v2.Popover); - Assert.Null (v2.Navigation); - Assert.Null (v2.Top); - Assert.Empty (v2.TopLevels); + + //Assert.Null (v2.Popover); + //Assert.Null (v2.Navigation); + Assert.Null (v2.Current); + Assert.Empty (v2.SessionStack); // Init should populate instance fields - v2.Init (null, "fake"); + v2.Init ("fake"); // After Init, Driver, Navigation, and Popover should be populated Assert.NotNull (v2.Driver); Assert.True (v2.Initialized); Assert.NotNull (v2.Popover); Assert.NotNull (v2.Navigation); - Assert.Null (v2.Top); // Top is still null until Run - - // Verify that static Application properties delegate to instance - Assert.Equal (v2.Driver, Application.Driver); - Assert.Equal (v2.Initialized, Application.Initialized); - Assert.Equal (v2.Popover, Application.Popover); - Assert.Equal (v2.Navigation, Application.Navigation); - Assert.Equal (v2.Top, Application.Top); - Assert.Same (v2.TopLevels, Application.TopLevels); + Assert.Null (v2.Current); // Top is still null until Run // Shutdown should clean up instance fields v2.Shutdown (); Assert.Null (v2.Driver); Assert.False (v2.Initialized); - Assert.Null (v2.Popover); - Assert.Null (v2.Navigation); - Assert.Null (v2.Top); - Assert.Empty (v2.TopLevels); - ApplicationImpl.ChangeInstance (orig); + //Assert.Null (v2.Popover); + //Assert.Null (v2.Navigation); + Assert.Null (v2.Current); + Assert.Empty (v2.SessionStack); } } diff --git a/Tests/UnitTests/Application/ApplicationPopoverTests.cs b/Tests/UnitTests/Application/ApplicationPopoverTests.cs index 9cee1ed7c..1ca1944d0 100644 --- a/Tests/UnitTests/Application/ApplicationPopoverTests.cs +++ b/Tests/UnitTests/Application/ApplicationPopoverTests.cs @@ -9,8 +9,7 @@ public class ApplicationPopoverTests try { // Arrange - Assert.Null (Application.Popover); - Application.Init (null, "fake"); + Application.Init ("fake"); // Act Assert.NotNull (Application.Popover); @@ -27,8 +26,8 @@ public class ApplicationPopoverTests try { // Arrange - Assert.Null (Application.Popover); - Application.Init (null, "fake"); + + Application.Init ("fake"); // Act Assert.NotNull (Application.Popover); @@ -36,7 +35,6 @@ public class ApplicationPopoverTests Application.Shutdown (); // Test - Assert.Null (Application.Popover); } finally { @@ -52,12 +50,11 @@ public class ApplicationPopoverTests try { // Arrange - Assert.Null (Application.Popover); - Application.Init (null, "fake"); + Application.Init ("fake"); Assert.NotNull (Application.Popover); Application.StopAfterFirstIteration = true; - top = new Toplevel (); + top = new (); SessionToken rs = Application.Begin (top); // Act @@ -81,15 +78,15 @@ public class ApplicationPopoverTests try { // Arrange - Assert.Null (Application.Popover); - Application.Init (null, "fake"); + Application.Init ("fake"); Application.StopAfterFirstIteration = true; - top = new Toplevel (); + top = new (); SessionToken rs = Application.Begin (top); PopoverTestClass? popover = new (); + Application.Popover?.Register (popover); Application.Popover?.Show (popover); Assert.True (popover.Visible); @@ -116,8 +113,8 @@ public class ApplicationPopoverTests try { // Arrange - Assert.Null (Application.Popover); - Application.Init (null, "fake"); + + Application.Init ("fake"); PopoverTestClass? popover = new (); @@ -140,8 +137,8 @@ public class ApplicationPopoverTests try { // Arrange - Assert.Null (Application.Popover); - Application.Init (null, "fake"); + + Application.Init ("fake"); PopoverTestClass? popover = new (); @@ -169,11 +166,11 @@ public class ApplicationPopoverTests try { // Arrange - Assert.Null (Application.Popover); - Application.Init (null, "fake"); + + Application.Init ("fake"); PopoverTestClass? popover = new (); - + Application.Popover?.Register (popover); Application.Popover?.Show (popover); Application.Popover?.DeRegister (popover); @@ -198,16 +195,16 @@ public class ApplicationPopoverTests try { // Arrange - Assert.Null (Application.Popover); - Application.Init (null, "fake"); - Application.Top = new Toplevel (); + + Application.Init ("fake"); + Application.Current = new (); PopoverTestClass? popover = new (); // Act Application.Popover?.Register (popover); // Assert - Assert.Equal (Application.Top, popover.Toplevel); + Assert.Equal (Application.Current, popover.Current); } finally { @@ -221,23 +218,23 @@ public class ApplicationPopoverTests try { // Arrange - Assert.Null (Application.Popover); - Application.Init (null, "fake"); - Application.Top = new Toplevel () { Id = "initialTop" }; - PopoverTestClass? popover = new (); - int keyDownEvents = 0; + Application.Init ("fake"); + Application.Current = new() { Id = "initialTop" }; + PopoverTestClass? popover = new () { }; + var keyDownEvents = 0; + popover.KeyDown += (s, e) => - { - keyDownEvents++; - e.Handled = true; - }; // Ensure it handles the key + { + keyDownEvents++; + e.Handled = true; + }; // Ensure it handles the key Application.Popover?.Register (popover); // Act Application.RaiseKeyDownEvent (Key.A); // Goes to initialTop - Application.Top = new Toplevel () { Id = "secondaryTop" }; + Application.Current = new() { Id = "secondaryTop" }; Application.RaiseKeyDownEvent (Key.A); // Goes to secondaryTop // Test @@ -268,9 +265,9 @@ public class ApplicationPopoverTests try { // Arrange - Assert.Null (Application.Popover); - Application.Init (null, "fake"); - Application.Top = new () + Application.Init ("fake"); + + Application.Current = new () { Frame = new (0, 0, 10, 10), Id = "top" @@ -282,10 +279,10 @@ public class ApplicationPopoverTests X = 1, Y = 1, Width = 2, - Height = 2, + Height = 2 }; - Application.Top.Add (view); + Application.Current.Add (view); popover = new () { @@ -293,7 +290,7 @@ public class ApplicationPopoverTests X = 5, Y = 5, Width = 3, - Height = 3, + Height = 3 }; // at 5,5 to 8,8 (screen) View? popoverSubView = new () @@ -302,14 +299,15 @@ public class ApplicationPopoverTests X = 1, Y = 1, Width = 1, - Height = 1, + Height = 1 }; popover.Add (popoverSubView); + Application.Popover?.Register (popover); Application.Popover?.Show (popover); - List found = View.GetViewsUnderLocation (new (mouseX, mouseY), ViewportSettingsFlags.TransparentMouse); + List found = view.GetViewsUnderLocation (new (mouseX, mouseY), ViewportSettingsFlags.TransparentMouse); string [] foundIds = found.Select (v => v!.Id).ToArray (); @@ -318,7 +316,7 @@ public class ApplicationPopoverTests finally { popover?.Dispose (); - Application.Top?.Dispose (); + Application.Current?.Dispose (); Application.ResetState (true); } } @@ -361,4 +359,4 @@ public class ApplicationPopoverTests DisposedCount++; } } -} \ No newline at end of file +} diff --git a/Tests/UnitTests/Application/ApplicationScreenTests.cs b/Tests/UnitTests/Application/ApplicationScreenTests.cs index fd4f27be8..9d4185adf 100644 --- a/Tests/UnitTests/Application/ApplicationScreenTests.cs +++ b/Tests/UnitTests/Application/ApplicationScreenTests.cs @@ -15,7 +15,7 @@ public class ApplicationScreenTests { // Arrange Application.ResetState (true); - Application.Init (null, "fake"); + Application.Init ("fake"); // Act Application.ClearScreenNextIteration = true; @@ -46,35 +46,35 @@ public class ApplicationScreenTests Assert.Equal (0, clearedContentsRaised); // Act - Application.Top!.SetNeedsLayout (); + Application.Current!.SetNeedsLayout (); Application.LayoutAndDraw (); // Assert Assert.Equal (0, clearedContentsRaised); // Act - Application.Top.X = 1; + Application.Current.X = 1; Application.LayoutAndDraw (); // Assert Assert.Equal (1, clearedContentsRaised); // Act - Application.Top.Width = 10; + Application.Current.Width = 10; Application.LayoutAndDraw (); // Assert Assert.Equal (2, clearedContentsRaised); // Act - Application.Top.Y = 1; + Application.Current.Y = 1; Application.LayoutAndDraw (); // Assert Assert.Equal (3, clearedContentsRaised); // Act - Application.Top.Height = 10; + Application.Current.Height = 10; Application.LayoutAndDraw (); // Assert diff --git a/Tests/UnitTests/Application/ApplicationTests.cs b/Tests/UnitTests/Application/ApplicationTests.cs index a0909f551..f9230fccd 100644 --- a/Tests/UnitTests/Application/ApplicationTests.cs +++ b/Tests/UnitTests/Application/ApplicationTests.cs @@ -21,120 +21,41 @@ public class ApplicationTests private readonly ITestOutputHelper _output; - private object _timeoutLock; - - [Fact (Skip = "Hangs with SetupFakeApplication")] - [SetupFakeApplication] + [Fact] public void AddTimeout_Fires () { - Assert.Null (_timeoutLock); - _timeoutLock = new (); + IApplication app = Application.Create (); + app.Init ("fake"); - uint timeoutTime = 250; - var initialized = false; - var iteration = 0; - var shutdown = false; - object timeout = null; - var timeoutCount = 0; + uint timeoutTime = 100; + var timeoutFired = false; - Application.InitializedChanged += OnApplicationOnInitializedChanged; + // Setup a timeout that will fire + app.AddTimeout ( + TimeSpan.FromMilliseconds (timeoutTime), + () => + { + timeoutFired = true; - _output.WriteLine ("Application.Run ().Dispose ().."); - Application.Run ().Dispose (); - _output.WriteLine ("Back from Application.Run ().Dispose ()"); + // Return false so the timer does not repeat + return false; + } + ); - Assert.True (initialized); - Assert.False (shutdown); + // The timeout has not fired yet + Assert.False (timeoutFired); - Assert.Equal (1, timeoutCount); - Application.Shutdown (); + // Block the thread to prove the timeout does not fire on a background thread + Thread.Sleep ((int)timeoutTime * 2); + Assert.False (timeoutFired); - Application.InitializedChanged -= OnApplicationOnInitializedChanged; + app.StopAfterFirstIteration = true; + app.Run ().Dispose (); - lock (_timeoutLock) - { - if (timeout is { }) - { - Application.RemoveTimeout (timeout); - timeout = null; - } - } + // The timeout should have fired + Assert.True (timeoutFired); - Assert.True (initialized); - Assert.True (shutdown); - -#if DEBUG_IDISPOSABLE - Assert.Empty (View.Instances); -#endif - lock (_timeoutLock) - { - _timeoutLock = null; - } - - return; - - void OnApplicationOnInitializedChanged (object s, EventArgs a) - { - if (a.Value) - { - Application.Iteration += OnApplicationOnIteration; - initialized = true; - - lock (_timeoutLock) - { - _output.WriteLine ($"Setting timeout for {timeoutTime}ms"); - timeout = Application.AddTimeout (TimeSpan.FromMilliseconds (timeoutTime), TimeoutCallback); - } - } - else - { - Application.Iteration -= OnApplicationOnIteration; - shutdown = true; - } - } - - bool TimeoutCallback () - { - lock (_timeoutLock) - { - _output.WriteLine ($"TimeoutCallback. Count: {++timeoutCount}. Application Iteration: {iteration}"); - - if (timeout is { }) - { - _output.WriteLine (" Nulling timeout."); - timeout = null; - } - } - - // False means "don't re-do timer and remove it" - return false; - } - - void OnApplicationOnIteration (object s, IterationEventArgs a) - { - lock (_timeoutLock) - { - if (timeoutCount > 0) - { - _output.WriteLine ($"Iteration #{iteration} - Timeout fired. Calling Application.RequestStop."); - Application.RequestStop (); - - return; - } - } - - iteration++; - - // Simulate a delay - Thread.Sleep ((int)timeoutTime / 10); - - // Worst case scenario - something went wrong - if (Application.Initialized && iteration > 25) - { - _output.WriteLine ($"Too many iterations ({iteration}): Calling Application.RequestStop."); - Application.RequestStop (); - } - } + app.Shutdown (); } [Fact] @@ -149,13 +70,13 @@ public class ApplicationTests [SetupFakeApplication] public void Begin_Sets_Application_Top_To_Console_Size () { - Assert.Null (Application.Top); + Assert.Null (Application.Current); Application.Driver!.SetScreenSize (80, 25); Toplevel top = new (); Application.Begin (top); - Assert.Equal (new (0, 0, 80, 25), Application.Top!.Frame); + Assert.Equal (new (0, 0, 80, 25), Application.Current!.Frame); Application.Driver!.SetScreenSize (5, 5); - Assert.Equal (new (0, 0, 5, 5), Application.Top!.Frame); + Assert.Equal (new (0, 0, 5, 5), Application.Current!.Frame); top.Dispose (); } @@ -163,21 +84,21 @@ public class ApplicationTests [SetupFakeApplication] public void End_And_Shutdown_Should_Not_Dispose_ApplicationTop () { - Assert.Null (Application.Top); + Assert.Null (Application.Current); SessionToken rs = Application.Begin (new ()); - Application.Top!.Title = "End_And_Shutdown_Should_Not_Dispose_ApplicationTop"; - Assert.Equal (rs.Toplevel, Application.Top); + Application.Current!.Title = "End_And_Shutdown_Should_Not_Dispose_ApplicationTop"; + Assert.Equal (rs.Toplevel, Application.Current); Application.End (rs); #if DEBUG_IDISPOSABLE Assert.True (rs.WasDisposed); - Assert.False (Application.Top!.WasDisposed); // Is true because the rs.Toplevel is the same as Application.Top + Assert.False (Application.Current!.WasDisposed); // Is true because the rs.Toplevel is the same as Application.Current #endif Assert.Null (rs.Toplevel); - Toplevel top = Application.Top; + Toplevel top = Application.Current; #if DEBUG_IDISPOSABLE Exception exception = Record.Exception (Application.Shutdown); @@ -211,12 +132,12 @@ public class ApplicationTests Assert.NotNull (sessionToken); Assert.Equal (rs, sessionToken); - Assert.Equal (topLevel, Application.Top); + Assert.Equal (topLevel, Application.Current); Application.SessionBegun -= newSessionTokenFn; Application.End (sessionToken); - Assert.NotNull (Application.Top); + Assert.NotNull (Application.Current); Assert.NotNull (Application.Driver); topLevel.Dispose (); @@ -240,7 +161,7 @@ public class ApplicationTests Application.QuitKey = Key.Q; Assert.Equal (Key.Q, Application.QuitKey); - Application.Init (null, "fake"); + Application.Init ("fake"); Assert.Equal (Key.Q, Application.QuitKey); } @@ -313,8 +234,6 @@ public class ApplicationTests // Mouse Application.LastMousePosition = new Point (1, 1); - Application.Navigation = new (); - Application.ResetState (); CheckReset (); @@ -327,7 +246,7 @@ public class ApplicationTests // Check that all fields and properties are set to their default values // Public Properties - Assert.Null (Application.Top); + Assert.Null (Application.Current); Assert.Null (Application.Mouse.MouseGrabView); // Don't check Application.ForceDriver @@ -350,7 +269,7 @@ public class ApplicationTests Assert.Equal (Application.GetSupportedCultures (), Application.SupportedCultures); Assert.Equal (Application.GetAvailableCulturesFromEmbeddedResources (), Application.SupportedCultures); Assert.Null (Application.MainThreadId); - Assert.Empty (Application.TopLevels); + Assert.Empty (Application.SessionStack); Assert.Empty (Application.CachedViewsUnderMouse); // Mouse @@ -358,10 +277,10 @@ public class ApplicationTests //Assert.Null (Application._lastMousePosition); // Navigation - Assert.Null (Application.Navigation); + // Assert.Null (Application.Navigation); // Popover - Assert.Null (Application.Popover); + //Assert.Null (Application.Popover); // Events - Can't check //Assert.Null (GetEventSubscribers (typeof (Application), "InitializedChanged")); @@ -380,7 +299,7 @@ public class ApplicationTests // Verify initial state is per spec //Pre_Init_State (); - Application.Init (null, "fake"); + Application.Init ("fake"); // Verify post-Init state is correct //Post_Init_State (); @@ -436,7 +355,7 @@ public class ApplicationTests public void Init_Unbalanced_Throws () { Assert.Throws (() => - Application.Init (null, "fake") + Application.Init ("fake") ); } @@ -445,7 +364,7 @@ public class ApplicationTests public void Init_Unbalanced_Throws2 () { // Now try the other way - Assert.Throws (() => Application.Init (null, "fake")); + Assert.Throws (() => Application.Init ("fake")); } [Fact] @@ -456,7 +375,7 @@ public class ApplicationTests // NOTE: Run, when called after Init has been called behaves differently than // when called if Init has not been called. Toplevel topLevel = new (); - Application.Init (null, "fake"); + Application.Init ("fake"); SessionToken sessionToken = null; @@ -472,18 +391,18 @@ public class ApplicationTests Assert.NotNull (sessionToken); Assert.Equal (rs, sessionToken); - Assert.Equal (topLevel, Application.Top); + Assert.Equal (topLevel, Application.Current); Application.SessionBegun -= newSessionTokenFn; Application.End (sessionToken); - Assert.NotNull (Application.Top); + Assert.NotNull (Application.Current); Assert.NotNull (Application.Driver); topLevel.Dispose (); Application.Shutdown (); - Assert.Null (Application.Top); + Assert.Null (Application.Current); Assert.Null (Application.Driver); } @@ -492,11 +411,11 @@ public class ApplicationTests public void Internal_Properties_Correct () { Assert.True (Application.Initialized); - Assert.Null (Application.Top); + Assert.Null (Application.Current); SessionToken rs = Application.Begin (new ()); - Assert.Equal (Application.Top, rs.Toplevel); + Assert.Equal (Application.Current, rs.Toplevel); Assert.Null (Application.Mouse.MouseGrabView); // public - Application.Top!.Dispose (); + Application.Current!.Dispose (); } // Invoke Tests @@ -509,7 +428,7 @@ public class ApplicationTests SessionToken rs = Application.Begin (top); var actionCalled = 0; - Application.Invoke (() => { actionCalled++; }); + Application.Invoke ((_) => { actionCalled++; }); Application.TimedEvents!.RunTimers (); Assert.Equal (1, actionCalled); top.Dispose (); @@ -520,7 +439,7 @@ public class ApplicationTests { var iteration = 0; - Application.Init (null, "fake"); + Application.Init ("fake"); Application.Iteration += Application_Iteration; Application.Run ().Dispose (); @@ -592,9 +511,9 @@ public class ApplicationTests // Run when already initialized or not with a Driver will not throw (because Window is derived from Toplevel) // Using another type not derived from Toplevel will throws at compile time Application.Run (); - Assert.True (Application.Top is Window); + Assert.True (Application.Current is Window); - Application.Top!.Dispose (); + Application.Current!.Dispose (); } [Fact] @@ -605,15 +524,15 @@ public class ApplicationTests // Run when already initialized or not with a Driver will not throw (because Window is derived from Toplevel) // Using another type not derived from Toplevel will throws at compile time Application.Run (null, "fake"); - Assert.True (Application.Top is Window); + Assert.True (Application.Current is Window); - Application.Top!.Dispose (); + Application.Current!.Dispose (); // Run when already initialized or not with a Driver will not throw (because Dialog is derived from Toplevel) Application.Run (null, "fake"); - Assert.True (Application.Top is Dialog); + Assert.True (Application.Current is Dialog); - Application.Top!.Dispose (); + Application.Current!.Dispose (); Application.Shutdown (); } @@ -621,7 +540,7 @@ public class ApplicationTests [SetupFakeApplication] public void Run_T_After_Init_Does_Not_Disposes_Application_Top () { - // Init doesn't create a Toplevel and assigned it to Application.Top + // Init doesn't create a Toplevel and assigned it to Application.Current // but Begin does var initTop = new Toplevel (); @@ -635,13 +554,13 @@ public class ApplicationTests initTop.Dispose (); Assert.True (initTop.WasDisposed); #endif - Application.Top!.Dispose (); + Application.Current!.Dispose (); return; void OnApplicationOnIteration (object s, IterationEventArgs a) { - Assert.NotEqual (initTop, Application.Top); + Assert.NotEqual (initTop, Application.Current); #if DEBUG_IDISPOSABLE Assert.False (initTop.WasDisposed); #endif @@ -658,7 +577,7 @@ public class ApplicationTests // Init has been called and we're passing no driver to Run. This is ok. Application.Run (); - Application.Top!.Dispose (); + Application.Current!.Dispose (); } [Fact] @@ -670,7 +589,7 @@ public class ApplicationTests // Init has been called, selecting FakeDriver; we're passing no driver to Run. Should be fine. Application.Run (); - Application.Top!.Dispose (); + Application.Current!.Dispose (); } [Fact] @@ -701,7 +620,7 @@ public class ApplicationTests // Init has NOT been called and we're passing a valid driver to Run. This is ok. Application.Run (null, "fake"); - Application.Top!.Dispose (); + Application.Current!.Dispose (); } [Fact] @@ -825,9 +744,9 @@ public class ApplicationTests Assert.NotNull (w); Assert.Equal (string.Empty, w.Title); // Valid - w has not been disposed. The user may want to run it again - Assert.NotNull (Application.Top); - Assert.Equal (w, Application.Top); - Assert.NotEqual (top, Application.Top); + Assert.NotNull (Application.Current); + Assert.Equal (w, Application.Current); + Assert.NotEqual (top, Application.Current); Application.Run (w); // Valid - w has not been disposed. @@ -841,7 +760,7 @@ public class ApplicationTests //exception = Record.Exception ( // () => Application.Run ( - // w)); // Invalid - w has been disposed. Run it in debug mode will throw, otherwise the user may want to run it again + // w)); // Invalid - w has not been disposed. Run it in debug mode will throw, otherwise the user may want to run it again //Assert.NotNull (exception); // TODO: Re-enable this when we are done debug logging of ctx.Source.Title in RaiseSelecting @@ -855,14 +774,14 @@ public class ApplicationTests [Fact] public void Run_Creates_Top_Without_Init () { - Assert.Null (Application.Top); + Assert.Null (Application.Current); Application.StopAfterFirstIteration = true; Application.Iteration += OnApplicationOnIteration; Toplevel top = Application.Run (null, "fake"); Application.Iteration -= OnApplicationOnIteration; #if DEBUG_IDISPOSABLE - Assert.Equal (top, Application.Top); + Assert.Equal (top, Application.Current); Assert.False (top.WasDisposed); Exception exception = Record.Exception (Application.Shutdown); Assert.NotNull (exception); @@ -875,38 +794,38 @@ public class ApplicationTests #if DEBUG_IDISPOSABLE Assert.True (top.WasDisposed); #endif - Assert.NotNull (Application.Top); + Assert.NotNull (Application.Current); Application.Shutdown (); - Assert.Null (Application.Top); + Assert.Null (Application.Current); return; - void OnApplicationOnIteration (object s, IterationEventArgs e) { Assert.NotNull (Application.Top); } + void OnApplicationOnIteration (object s, IterationEventArgs e) { Assert.NotNull (Application.Current); } } [Fact] public void Run_T_Creates_Top_Without_Init () { - Assert.Null (Application.Top); + Assert.Null (Application.Current); Application.StopAfterFirstIteration = true; Application.Run (null, "fake"); #if DEBUG_IDISPOSABLE - Assert.False (Application.Top!.WasDisposed); + Assert.False (Application.Current!.WasDisposed); Exception exception = Record.Exception (Application.Shutdown); Assert.NotNull (exception); - Assert.False (Application.Top!.WasDisposed); + Assert.False (Application.Current!.WasDisposed); // It's up to caller to dispose it - Application.Top!.Dispose (); - Assert.True (Application.Top!.WasDisposed); + Application.Current!.Dispose (); + Assert.True (Application.Current!.WasDisposed); #endif - Assert.NotNull (Application.Top); + Assert.NotNull (Application.Current); Application.Shutdown (); - Assert.Null (Application.Top); + Assert.Null (Application.Current); } [Fact] @@ -920,35 +839,35 @@ public class ApplicationTests // the new(Toplevel) may be a derived class that is possible using Application static // properties that is only available after the Application.Init was called - Assert.Null (Application.Top); + Assert.Null (Application.Current); Assert.Throws (() => Application.Run (new Toplevel ())); - Application.Init (null, "fake"); + Application.Init ("fake"); - Application.Iteration += OnApplicationOnIteration; + Application.Iteration += OnApplication_OnIteration; Application.Run (new Toplevel ()); - Application.Iteration -= OnApplicationOnIteration; + Application.Iteration -= OnApplication_OnIteration; #if DEBUG_IDISPOSABLE - Assert.False (Application.Top!.WasDisposed); + Assert.False (Application.Current!.WasDisposed); Exception exception = Record.Exception (Application.Shutdown); Assert.NotNull (exception); - Assert.False (Application.Top!.WasDisposed); + Assert.False (Application.Current!.WasDisposed); // It's up to caller to dispose it - Application.Top!.Dispose (); - Assert.True (Application.Top!.WasDisposed); + Application.Current!.Dispose (); + Assert.True (Application.Current!.WasDisposed); #endif - Assert.NotNull (Application.Top); + Assert.NotNull (Application.Current); Application.Shutdown (); - Assert.Null (Application.Top); + Assert.Null (Application.Current); return; - void OnApplicationOnIteration (object s, IterationEventArgs e) + void OnApplication_OnIteration (object s, IterationEventArgs e) { - Assert.NotNull (Application.Top); + Assert.NotNull (Application.Current); Application.RequestStop (); } } @@ -960,7 +879,7 @@ public class ApplicationTests public void Run_T_With_V2_Driver_Does_Not_Call_ResetState_After_Init () { Assert.False (Application.Initialized); - Application.Init (null, "fake"); + Application.Init ("fake"); Assert.True (Application.Initialized); Task.Run (() => { Task.Delay (300).Wait (); }) @@ -968,14 +887,14 @@ public class ApplicationTests (t, _) => { // no longer loading - Application.Invoke (() => { Application.RequestStop (); }); + Application.Invoke ((app) => { app.RequestStop (); }); }, TaskScheduler.FromCurrentSynchronizationContext ()); Application.Run (); Assert.NotNull (Application.Driver); - Assert.NotNull (Application.Top); - Assert.False (Application.Top!.Running); - Application.Top!.Dispose (); + Assert.NotNull (Application.Current); + Assert.False (Application.Current!.Running); + Application.Current!.Dispose (); Application.Shutdown (); } diff --git a/Tests/UnitTests/Application/CursorTests.cs b/Tests/UnitTests/Application/CursorTests.cs index 69d472290..bf5cb1247 100644 --- a/Tests/UnitTests/Application/CursorTests.cs +++ b/Tests/UnitTests/Application/CursorTests.cs @@ -1,5 +1,4 @@ -using UnitTests; -using Xunit.Abstractions; +using Xunit.Abstractions; namespace UnitTests.ApplicationTests; @@ -7,22 +6,20 @@ public class CursorTests { private readonly ITestOutputHelper _output; - public CursorTests (ITestOutputHelper output) - { - _output = output; - } + public CursorTests (ITestOutputHelper output) { _output = output; } private class TestView : View { public Point? TestLocation { get; set; } - /// + /// public override Point? PositionCursor () { if (TestLocation.HasValue && HasFocus) { - Driver.SetCursorVisibility (CursorVisibility.Default); + Driver?.SetCursorVisibility (CursorVisibility.Default); } + return TestLocation; } } @@ -31,7 +28,6 @@ public class CursorTests [AutoInitShutdown] public void PositionCursor_No_Focus_Returns_False () { - Application.Navigation = new (); Application.Navigation.SetFocused (null); Assert.False (Application.PositionCursor ()); @@ -40,7 +36,7 @@ public class CursorTests { CanFocus = false, Width = 1, - Height = 1, + Height = 1 }; view.TestLocation = new Point (0, 0); Assert.False (Application.PositionCursor ()); @@ -50,12 +46,11 @@ public class CursorTests [AutoInitShutdown] public void PositionCursor_No_Position_Returns_False () { - Application.Navigation = new (); TestView view = new () { CanFocus = false, Width = 1, - Height = 1, + Height = 1 }; view.CanFocus = true; @@ -67,11 +62,10 @@ public class CursorTests [AutoInitShutdown] public void PositionCursor_No_IntersectSuperView_Returns_False () { - Application.Navigation = new (); View superView = new () { Width = 1, - Height = 1, + Height = 1 }; TestView view = new () @@ -80,7 +74,7 @@ public class CursorTests X = 1, Y = 1, Width = 1, - Height = 1, + Height = 1 }; superView.Add (view); @@ -94,11 +88,10 @@ public class CursorTests [AutoInitShutdown] public void PositionCursor_Position_OutSide_SuperView_Returns_False () { - Application.Navigation = new (); View superView = new () { Width = 1, - Height = 1, + Height = 1 }; TestView view = new () @@ -107,7 +100,7 @@ public class CursorTests X = 0, Y = 0, Width = 2, - Height = 2, + Height = 2 }; superView.Add (view); @@ -121,12 +114,12 @@ public class CursorTests [AutoInitShutdown] public void PositionCursor_Focused_With_Position_Returns_True () { - Application.Navigation = new (); TestView view = new () { CanFocus = false, Width = 1, Height = 1, + App = ApplicationImpl.Instance }; view.CanFocus = true; view.SetFocus (); @@ -138,12 +131,11 @@ public class CursorTests [AutoInitShutdown] public void PositionCursor_Defaults_Invisible () { - Application.Navigation = new (); View view = new () { CanFocus = true, Width = 1, - Height = 1, + Height = 1 }; view.SetFocus (); diff --git a/Tests/UnitTests/Application/MainLoopCoordinatorTests.cs b/Tests/UnitTests/Application/MainLoopCoordinatorTests.cs index 6c7f1d123..b0e1e5708 100644 --- a/Tests/UnitTests/Application/MainLoopCoordinatorTests.cs +++ b/Tests/UnitTests/Application/MainLoopCoordinatorTests.cs @@ -26,7 +26,7 @@ public class MainLoopCoordinatorTests // StartAsync boots the main loop and the input thread. But if the input class bombs // on startup it is important that the exception surface at the call site and not lost - var ex = await Assert.ThrowsAsync(c.StartInputTaskAsync); + var ex = await Assert.ThrowsAsync(() => c.StartInputTaskAsync (null)); Assert.Equal ("Crash on boot", ex.InnerExceptions [0].Message); diff --git a/Tests/UnitTests/Application/Mouse/ApplicationMouseEnterLeaveTests.cs b/Tests/UnitTests/Application/Mouse/ApplicationMouseEnterLeaveTests.cs index cbe800a98..06c3cc4d1 100644 --- a/Tests/UnitTests/Application/Mouse/ApplicationMouseEnterLeaveTests.cs +++ b/Tests/UnitTests/Application/Mouse/ApplicationMouseEnterLeaveTests.cs @@ -41,9 +41,9 @@ public class ApplicationMouseEnterLeaveTests public void RaiseMouseEnterLeaveEvents_MouseEntersView_CallsOnMouseEnter () { // Arrange - Application.Top = new () { Frame = new (0, 0, 10, 10) }; + Application.Current = new () { Frame = new (0, 0, 10, 10) }; var view = new TestView (); - Application.Top.Add (view); + Application.Current.Add (view); var mousePosition = new Point (1, 1); List currentViewsUnderMouse = new () { view }; @@ -66,7 +66,7 @@ public class ApplicationMouseEnterLeaveTests finally { // Cleanup - Application.Top?.Dispose (); + Application.Current?.Dispose (); Application.ResetState (); } } @@ -75,9 +75,9 @@ public class ApplicationMouseEnterLeaveTests public void RaiseMouseEnterLeaveEvents_MouseLeavesView_CallsOnMouseLeave () { // Arrange - Application.Top = new () { Frame = new (0, 0, 10, 10) }; + Application.Current = new () { Frame = new (0, 0, 10, 10) }; var view = new TestView (); - Application.Top.Add (view); + Application.Current.Add (view); var mousePosition = new Point (0, 0); List currentViewsUnderMouse = new (); var mouseEvent = new MouseEventArgs (); @@ -97,7 +97,7 @@ public class ApplicationMouseEnterLeaveTests finally { // Cleanup - Application.Top?.Dispose (); + Application.Current?.Dispose (); Application.ResetState (); } } @@ -106,7 +106,7 @@ public class ApplicationMouseEnterLeaveTests public void RaiseMouseEnterLeaveEvents_MouseMovesBetweenAdjacentViews_CallsOnMouseEnterAndLeave () { // Arrange - Application.Top = new () { Frame = new (0, 0, 10, 10) }; + Application.Current = new () { Frame = new (0, 0, 10, 10) }; var view1 = new TestView (); // at 1,1 to 2,2 var view2 = new TestView () // at 2,2 to 3,3 @@ -114,8 +114,8 @@ public class ApplicationMouseEnterLeaveTests X = 2, Y = 2 }; - Application.Top.Add (view1); - Application.Top.Add (view2); + Application.Current.Add (view1); + Application.Current.Add (view2); Application.CachedViewsUnderMouse.Clear (); @@ -126,7 +126,7 @@ public class ApplicationMouseEnterLeaveTests Application.RaiseMouseEnterLeaveEvents ( mousePosition, - View.GetViewsUnderLocation (mousePosition, ViewportSettingsFlags.TransparentMouse)); + Application.Current.GetViewsUnderLocation (mousePosition, ViewportSettingsFlags.TransparentMouse)); // Assert Assert.Equal (0, view1.OnMouseEnterCalled); @@ -139,7 +139,7 @@ public class ApplicationMouseEnterLeaveTests Application.RaiseMouseEnterLeaveEvents ( mousePosition, - View.GetViewsUnderLocation (mousePosition, ViewportSettingsFlags.TransparentMouse)); + Application.Current.GetViewsUnderLocation (mousePosition, ViewportSettingsFlags.TransparentMouse)); // Assert Assert.Equal (1, view1.OnMouseEnterCalled); @@ -152,7 +152,7 @@ public class ApplicationMouseEnterLeaveTests Application.RaiseMouseEnterLeaveEvents ( mousePosition, - View.GetViewsUnderLocation (mousePosition, ViewportSettingsFlags.TransparentMouse)); + Application.Current.GetViewsUnderLocation (mousePosition, ViewportSettingsFlags.TransparentMouse)); // Assert Assert.Equal (1, view1.OnMouseEnterCalled); @@ -165,7 +165,7 @@ public class ApplicationMouseEnterLeaveTests Application.RaiseMouseEnterLeaveEvents ( mousePosition, - View.GetViewsUnderLocation (mousePosition, ViewportSettingsFlags.TransparentMouse)); + Application.Current.GetViewsUnderLocation (mousePosition, ViewportSettingsFlags.TransparentMouse)); // Assert Assert.Equal (1, view1.OnMouseEnterCalled); @@ -178,7 +178,7 @@ public class ApplicationMouseEnterLeaveTests Application.RaiseMouseEnterLeaveEvents ( mousePosition, - View.GetViewsUnderLocation (mousePosition, ViewportSettingsFlags.TransparentMouse)); + Application.Current.GetViewsUnderLocation (mousePosition, ViewportSettingsFlags.TransparentMouse)); // Assert Assert.Equal (1, view1.OnMouseEnterCalled); @@ -189,7 +189,7 @@ public class ApplicationMouseEnterLeaveTests finally { // Cleanup - Application.Top?.Dispose (); + Application.Current?.Dispose (); Application.ResetState (); } } @@ -198,9 +198,9 @@ public class ApplicationMouseEnterLeaveTests public void RaiseMouseEnterLeaveEvents_NoViewsUnderMouse_DoesNotCallOnMouseEnterOrLeave () { // Arrange - Application.Top = new () { Frame = new (0, 0, 10, 10) }; + Application.Current = new () { Frame = new (0, 0, 10, 10) }; var view = new TestView (); - Application.Top.Add (view); + Application.Current.Add (view); var mousePosition = new Point (0, 0); List currentViewsUnderMouse = new (); var mouseEvent = new MouseEventArgs (); @@ -219,7 +219,7 @@ public class ApplicationMouseEnterLeaveTests finally { // Cleanup - Application.Top?.Dispose (); + Application.Current?.Dispose (); Application.ResetState (); } } @@ -228,7 +228,7 @@ public class ApplicationMouseEnterLeaveTests public void RaiseMouseEnterLeaveEvents_MouseMovesBetweenOverlappingPeerViews_CallsOnMouseEnterAndLeave () { // Arrange - Application.Top = new () { Frame = new (0, 0, 10, 10) }; + Application.Current = new () { Frame = new (0, 0, 10, 10) }; var view1 = new TestView { @@ -241,8 +241,8 @@ public class ApplicationMouseEnterLeaveTests X = 2, Y = 2 }; - Application.Top.Add (view1); - Application.Top.Add (view2); + Application.Current.Add (view1); + Application.Current.Add (view2); Application.CachedViewsUnderMouse.Clear (); @@ -253,7 +253,7 @@ public class ApplicationMouseEnterLeaveTests Application.RaiseMouseEnterLeaveEvents ( mousePosition, - View.GetViewsUnderLocation (mousePosition, ViewportSettingsFlags.TransparentMouse)); + Application.Current.GetViewsUnderLocation (mousePosition, ViewportSettingsFlags.TransparentMouse)); // Assert Assert.Equal (0, view1.OnMouseEnterCalled); @@ -266,7 +266,7 @@ public class ApplicationMouseEnterLeaveTests Application.RaiseMouseEnterLeaveEvents ( mousePosition, - View.GetViewsUnderLocation (mousePosition, ViewportSettingsFlags.TransparentMouse)); + Application.Current.GetViewsUnderLocation (mousePosition, ViewportSettingsFlags.TransparentMouse)); // Assert Assert.Equal (1, view1.OnMouseEnterCalled); @@ -279,7 +279,7 @@ public class ApplicationMouseEnterLeaveTests Application.RaiseMouseEnterLeaveEvents ( mousePosition, - View.GetViewsUnderLocation (mousePosition, ViewportSettingsFlags.TransparentMouse)); + Application.Current.GetViewsUnderLocation (mousePosition, ViewportSettingsFlags.TransparentMouse)); // Assert Assert.Equal (1, view1.OnMouseEnterCalled); @@ -292,7 +292,7 @@ public class ApplicationMouseEnterLeaveTests Application.RaiseMouseEnterLeaveEvents ( mousePosition, - View.GetViewsUnderLocation (mousePosition, ViewportSettingsFlags.TransparentMouse)); + Application.Current.GetViewsUnderLocation (mousePosition, ViewportSettingsFlags.TransparentMouse)); // Assert Assert.Equal (1, view1.OnMouseEnterCalled); @@ -305,7 +305,7 @@ public class ApplicationMouseEnterLeaveTests Application.RaiseMouseEnterLeaveEvents ( mousePosition, - View.GetViewsUnderLocation (mousePosition, ViewportSettingsFlags.TransparentMouse)); + Application.Current.GetViewsUnderLocation (mousePosition, ViewportSettingsFlags.TransparentMouse)); // Assert Assert.Equal (1, view1.OnMouseEnterCalled); @@ -318,7 +318,7 @@ public class ApplicationMouseEnterLeaveTests Application.RaiseMouseEnterLeaveEvents ( mousePosition, - View.GetViewsUnderLocation (mousePosition, ViewportSettingsFlags.TransparentMouse)); + Application.Current.GetViewsUnderLocation (mousePosition, ViewportSettingsFlags.TransparentMouse)); // Assert Assert.Equal (1, view1.OnMouseEnterCalled); @@ -329,7 +329,7 @@ public class ApplicationMouseEnterLeaveTests finally { // Cleanup - Application.Top?.Dispose (); + Application.Current?.Dispose (); Application.ResetState (); } } @@ -338,7 +338,7 @@ public class ApplicationMouseEnterLeaveTests public void RaiseMouseEnterLeaveEvents_MouseMovesBetweenOverlappingSubViews_CallsOnMouseEnterAndLeave () { // Arrange - Application.Top = new () { Frame = new (0, 0, 10, 10) }; + Application.Current = new () { Frame = new (0, 0, 10, 10) }; var view1 = new TestView { @@ -358,7 +358,7 @@ public class ApplicationMouseEnterLeaveTests Arrangement = ViewArrangement.Overlapped }; // at 2,2 to 4,4 (screen) view1.Add (subView); - Application.Top.Add (view1); + Application.Current.Add (view1); Application.CachedViewsUnderMouse.Clear (); @@ -372,7 +372,7 @@ public class ApplicationMouseEnterLeaveTests Application.RaiseMouseEnterLeaveEvents ( mousePosition, - View.GetViewsUnderLocation (mousePosition, ViewportSettingsFlags.TransparentMouse)); + Application.Current.GetViewsUnderLocation (mousePosition, ViewportSettingsFlags.TransparentMouse)); // Assert Assert.Equal (0, view1.OnMouseEnterCalled); @@ -385,7 +385,7 @@ public class ApplicationMouseEnterLeaveTests Application.RaiseMouseEnterLeaveEvents ( mousePosition, - View.GetViewsUnderLocation (mousePosition, ViewportSettingsFlags.TransparentMouse)); + Application.Current.GetViewsUnderLocation (mousePosition, ViewportSettingsFlags.TransparentMouse)); // Assert Assert.Equal (1, view1.OnMouseEnterCalled); @@ -398,7 +398,7 @@ public class ApplicationMouseEnterLeaveTests Application.RaiseMouseEnterLeaveEvents ( mousePosition, - View.GetViewsUnderLocation (mousePosition, ViewportSettingsFlags.TransparentMouse)); + Application.Current.GetViewsUnderLocation (mousePosition, ViewportSettingsFlags.TransparentMouse)); // Assert Assert.Equal (1, view1.OnMouseEnterCalled); @@ -411,7 +411,7 @@ public class ApplicationMouseEnterLeaveTests Application.RaiseMouseEnterLeaveEvents ( mousePosition, - View.GetViewsUnderLocation (mousePosition, ViewportSettingsFlags.TransparentMouse)); + Application.Current.GetViewsUnderLocation (mousePosition, ViewportSettingsFlags.TransparentMouse)); // Assert Assert.Equal (1, view1.OnMouseEnterCalled); @@ -424,7 +424,7 @@ public class ApplicationMouseEnterLeaveTests Application.RaiseMouseEnterLeaveEvents ( mousePosition, - View.GetViewsUnderLocation (mousePosition, ViewportSettingsFlags.TransparentMouse)); + Application.Current.GetViewsUnderLocation (mousePosition, ViewportSettingsFlags.TransparentMouse)); // Assert Assert.Equal (2, view1.OnMouseEnterCalled); @@ -437,7 +437,7 @@ public class ApplicationMouseEnterLeaveTests Application.RaiseMouseEnterLeaveEvents ( mousePosition, - View.GetViewsUnderLocation (mousePosition, ViewportSettingsFlags.TransparentMouse)); + Application.Current.GetViewsUnderLocation (mousePosition, ViewportSettingsFlags.TransparentMouse)); // Assert Assert.Equal (2, view1.OnMouseEnterCalled); @@ -450,7 +450,7 @@ public class ApplicationMouseEnterLeaveTests Application.RaiseMouseEnterLeaveEvents ( mousePosition, - View.GetViewsUnderLocation (mousePosition, ViewportSettingsFlags.TransparentMouse)); + Application.Current.GetViewsUnderLocation (mousePosition, ViewportSettingsFlags.TransparentMouse)); // Assert Assert.Equal (2, view1.OnMouseEnterCalled); @@ -463,7 +463,7 @@ public class ApplicationMouseEnterLeaveTests Application.RaiseMouseEnterLeaveEvents ( mousePosition, - View.GetViewsUnderLocation (mousePosition, ViewportSettingsFlags.TransparentMouse)); + Application.Current.GetViewsUnderLocation (mousePosition, ViewportSettingsFlags.TransparentMouse)); // Assert Assert.Equal (3, view1.OnMouseEnterCalled); @@ -474,7 +474,7 @@ public class ApplicationMouseEnterLeaveTests finally { // Cleanup - Application.Top?.Dispose (); + Application.Current?.Dispose (); Application.ResetState (); } } diff --git a/Tests/UnitTests/Application/Mouse/ApplicationMouseTests.cs b/Tests/UnitTests/Application/Mouse/ApplicationMouseTests.cs index 0451264ed..6116bcf63 100644 --- a/Tests/UnitTests/Application/Mouse/ApplicationMouseTests.cs +++ b/Tests/UnitTests/Application/Mouse/ApplicationMouseTests.cs @@ -202,15 +202,15 @@ public class ApplicationMouseTests var clicked = false; - Application.Top = new Toplevel () + Application.Current = new Toplevel () { Id = "top", }; - Application.Top.X = 0; - Application.Top.Y = 0; - Application.Top.Width = size.Width * 2; - Application.Top.Height = size.Height * 2; - Application.Top.BorderStyle = LineStyle.None; + Application.Current.X = 0; + Application.Current.Y = 0; + Application.Current.Width = size.Width * 2; + Application.Current.Height = size.Height * 2; + Application.Current.BorderStyle = LineStyle.None; var view = new View { Id = "view", X = pos.X, Y = pos.Y, Width = size.Width, Height = size.Height }; @@ -218,7 +218,7 @@ public class ApplicationMouseTests view.BorderStyle = LineStyle.Single; view.CanFocus = true; - Application.Top.Add (view); + Application.Current.Add (view); var mouseEvent = new MouseEventArgs { Position = new (clickX, clickY), ScreenPosition = new (clickX, clickY), Flags = MouseFlags.Button1Clicked }; @@ -231,7 +231,7 @@ public class ApplicationMouseTests Application.RaiseMouseEvent (mouseEvent); Assert.Equal (expectedClicked, clicked); - Application.Top.Dispose (); + Application.Current.Dispose (); Application.ResetState (ignoreDisposed: true); } diff --git a/Tests/UnitTests/Application/SessionTokenTests.cs b/Tests/UnitTests/Application/SessionTokenTests.cs index d01bc1c59..1e642d04d 100644 --- a/Tests/UnitTests/Application/SessionTokenTests.cs +++ b/Tests/UnitTests/Application/SessionTokenTests.cs @@ -1,4 +1,5 @@ +#nullable enable namespace UnitTests.ApplicationTests; /// These tests focus on Application.SessionToken and the various ways it can be changed. @@ -19,14 +20,16 @@ public class SessionTokenTests public void Begin_End_Cleans_Up_SessionToken () { // Test null Toplevel - Assert.Throws (() => Application.Begin (null)); + Assert.Throws (() => Application.Begin (null!)); - var top = new Toplevel (); + Assert.NotNull (Application.Driver); + + Toplevel top = new Toplevel (); SessionToken rs = Application.Begin (top); Assert.NotNull (rs); Application.End (rs); - Assert.NotNull (Application.Top); + Assert.NotNull (Application.Current); // v2 does not use main loop, it uses MainLoop and its internal //Assert.NotNull (Application.MainLoop); @@ -42,7 +45,7 @@ public class SessionTokenTests [Fact] public void Dispose_Cleans_Up_SessionToken () { - var rs = new SessionToken (null); + var rs = new SessionToken (null!); Assert.NotNull (rs); // Should not throw because Toplevel was null @@ -57,7 +60,7 @@ public class SessionTokenTests // Should throw because Toplevel was not cleaned up Assert.Throws (() => rs.Dispose ()); - rs.Toplevel.Dispose (); + rs.Toplevel?.Dispose (); rs.Toplevel = null; rs.Dispose (); #if DEBUG_IDISPOSABLE @@ -69,7 +72,7 @@ public class SessionTokenTests [Fact] public void New_Creates_SessionToken () { - var rs = new SessionToken (null); + var rs = new SessionToken (null!); Assert.Null (rs.Toplevel); var top = new Toplevel (); diff --git a/Tests/UnitTests/Application/SynchronizatonContextTests.cs b/Tests/UnitTests/Application/SynchronizatonContextTests.cs index 9f8d34b0e..915cb3024 100644 --- a/Tests/UnitTests/Application/SynchronizatonContextTests.cs +++ b/Tests/UnitTests/Application/SynchronizatonContextTests.cs @@ -9,7 +9,7 @@ public class SyncrhonizationContextTests [Fact] public void SynchronizationContext_CreateCopy () { - Application.Init (null, "fake"); + Application.Init ("fake"); SynchronizationContext context = SynchronizationContext.Current; Assert.NotNull (context); @@ -31,7 +31,7 @@ public class SyncrhonizationContextTests { lock (_lockPost) { - Application.Init (null, driverName: driverName); + Application.Init (driverName); SynchronizationContext context = SynchronizationContext.Current; @@ -39,7 +39,7 @@ public class SyncrhonizationContextTests Task.Run (() => { - while (Application.Top is null || Application.Top is { Running: false }) + while (Application.Current is null || Application.Current is { Running: false }) { Thread.Sleep (500); } @@ -56,7 +56,7 @@ public class SyncrhonizationContextTests null ); - if (Application.Top is { Running: true }) + if (Application.Current is { Running: true }) { Assert.False (success); } diff --git a/Tests/UnitTests/Application/TimedEventsTests.cs b/Tests/UnitTests/Application/TimedEventsTests.cs index 4877a6b2c..98d061159 100644 --- a/Tests/UnitTests/Application/TimedEventsTests.cs +++ b/Tests/UnitTests/Application/TimedEventsTests.cs @@ -134,4 +134,35 @@ public class TimedEventsTests Assert.Equal (expected, executeCount); } + + [Fact] + public void StopAll_Stops_All_Timeouts () + { + var timedEvents = new TimedEvents (); + var executeCount = 0; + var expected = 100; + + for (var i = 0; i < expected; i++) + { + timedEvents.Add ( + TimeSpan.Zero, + () => + { + Interlocked.Increment (ref executeCount); + + return false; + }); + } + + Assert.Equal (expected, timedEvents.Timeouts.Count); + + timedEvents.StopAll (); + + Assert.Empty (timedEvents.Timeouts); + + // Run timers once + timedEvents.RunTimers (); + + Assert.Equal (0, executeCount); + } } diff --git a/Tests/UnitTests/AutoInitShutdownAttribute.cs b/Tests/UnitTests/AutoInitShutdownAttribute.cs index 0b3d2af07..a0536503b 100644 --- a/Tests/UnitTests/AutoInitShutdownAttribute.cs +++ b/Tests/UnitTests/AutoInitShutdownAttribute.cs @@ -25,20 +25,16 @@ public class AutoInitShutdownAttribute : BeforeAfterTestAttribute /// be used when Application.Init is called. If not specified FakeDriver will be used. Only valid if /// is true. /// - /// If true and is true, the test will fail. public AutoInitShutdownAttribute ( bool autoInit = true, - string forceDriver = null, - bool verifyShutdown = false + string forceDriver = null ) { - AutoInit = autoInit; + _autoInit = autoInit; CultureInfo.DefaultThreadCurrentUICulture = CultureInfo.GetCultureInfo ("en-US"); _forceDriver = forceDriver; - _verifyShutdown = verifyShutdown; } - private readonly bool _verifyShutdown; private readonly string _forceDriver; private IDisposable _v2Cleanup; @@ -51,15 +47,10 @@ public class AutoInitShutdownAttribute : BeforeAfterTestAttribute _v2Cleanup?.Dispose (); - if (AutoInit) + if (_autoInit) { - // try + try { - if (!_verifyShutdown) - { - Application.ResetState (ignoreDisposed: true); - } - Application.Shutdown (); #if DEBUG_IDISPOSABLE if (View.Instances.Count == 0) @@ -74,14 +65,15 @@ public class AutoInitShutdownAttribute : BeforeAfterTestAttribute } //catch (Exception e) //{ - // Assert.Fail ($"Application.Shutdown threw an exception after the test exited: {e}"); + // Debug.WriteLine ($"Application.Shutdown threw an exception after the test exited: {e}"); //} - //finally + finally { #if DEBUG_IDISPOSABLE View.Instances.Clear (); Application.ResetState (true); #endif + ApplicationImpl.SetInstance (null); } } @@ -102,7 +94,7 @@ public class AutoInitShutdownAttribute : BeforeAfterTestAttribute //Debug.Assert(!CM.IsEnabled, "Some other test left ConfigurationManager enabled."); - if (AutoInit) + if (_autoInit) { #if DEBUG_IDISPOSABLE View.EnableDebugIDisposableAsserts = true; @@ -117,7 +109,7 @@ public class AutoInitShutdownAttribute : BeforeAfterTestAttribute View.Instances.Clear (); } #endif - if (string.IsNullOrEmpty(_forceDriver) || _forceDriver.ToLowerInvariant () == "fake") + if (string.IsNullOrEmpty (_forceDriver) || _forceDriver.ToLowerInvariant () == "fake") { var fa = new FakeApplicationFactory (); _v2Cleanup = fa.SetupFakeApplication (); @@ -131,7 +123,7 @@ public class AutoInitShutdownAttribute : BeforeAfterTestAttribute } } - private bool AutoInit { get; } + private bool _autoInit { get; } /// /// Runs a single iteration of the main loop (layout, draw, run timed events etc.) diff --git a/Tests/UnitTests/Configuration/ThemeScopeTests.cs b/Tests/UnitTests/Configuration/ThemeScopeTests.cs index 25c881d64..ca1e05aa1 100644 --- a/Tests/UnitTests/Configuration/ThemeScopeTests.cs +++ b/Tests/UnitTests/Configuration/ThemeScopeTests.cs @@ -86,6 +86,43 @@ public class ThemeScopeTests Disable (true); } + + [Fact] + public void DeSerialize_Themes_UpdateFrom_Updates () + { + Enable (ConfigLocations.HardCoded); + + IDictionary initial = ThemeManager.Themes!; + + string serialized = """ + { + "Default": { + "Button.DefaultShadow": "None" + } + } + """; + ConcurrentDictionary? deserialized = + JsonSerializer.Deserialize> (serialized, SerializerContext.Options); + + ShadowStyle initialShadowStyle = (ShadowStyle)(initial! ["Default"] ["Button.DefaultShadow"].PropertyValue!); + Assert.Equal (ShadowStyle.Opaque, initialShadowStyle); + + ShadowStyle deserializedShadowStyle = (ShadowStyle)(deserialized! ["Default"] ["Button.DefaultShadow"].PropertyValue!); + Assert.Equal (ShadowStyle.None, deserializedShadowStyle); + + initial ["Default"].UpdateFrom (deserialized ["Default"]); + initialShadowStyle = (ShadowStyle)(initial! ["Default"] ["Button.DefaultShadow"].PropertyValue!); + Assert.Equal (ShadowStyle.None, initialShadowStyle); + + Assert.Equal(ShadowStyle.Opaque, Button.DefaultShadow); + initial ["Default"].Apply (); + Assert.Equal (ShadowStyle.None, Button.DefaultShadow); + + Disable (true); + Assert.Equal (ShadowStyle.Opaque, Button.DefaultShadow); + + } + [Fact] public void Serialize_New_RoundTrip () { diff --git a/Tests/UnitTests/Dialogs/DialogTests.cs b/Tests/UnitTests/Dialogs/DialogTests.cs index b37f3225d..a4ba9591a 100644 --- a/Tests/UnitTests/Dialogs/DialogTests.cs +++ b/Tests/UnitTests/Dialogs/DialogTests.cs @@ -902,8 +902,8 @@ public class DialogTests (ITestOutputHelper output) #if DEBUG_IDISPOSABLE Assert.False (dlg.WasDisposed); - Assert.False (Application.Top!.WasDisposed); - Assert.Equal (dlg, Application.Top); + Assert.False (Application.Current!.WasDisposed); + Assert.Equal (dlg, Application.Current); #endif Assert.True (dlg.Canceled); @@ -925,8 +925,8 @@ public class DialogTests (ITestOutputHelper output) Application.Run (dlg2); Assert.True (dlg.WasDisposed); - Assert.False (Application.Top.WasDisposed); - Assert.Equal (dlg2, Application.Top); + Assert.False (Application.Current.WasDisposed); + Assert.Equal (dlg2, Application.Current); Assert.False (dlg2.WasDisposed); dlg2.Dispose (); @@ -937,10 +937,10 @@ public class DialogTests (ITestOutputHelper output) //Assert.NotNull (exception); //Assert.StartsWith ("Cannot access a disposed object.", exception.Message); - Assert.True (Application.Top.WasDisposed); + Assert.True (Application.Current.WasDisposed); Application.Shutdown (); Assert.True (dlg2.WasDisposed); - Assert.Null (Application.Top); + Assert.Null (Application.Current); #endif return; @@ -1174,8 +1174,8 @@ public class DialogTests (ITestOutputHelper output) switch (iterations) { case 0: - Application.Top!.SetNeedsLayout (); - Application.Top.SetNeedsDraw (); + Application.Current!.SetNeedsLayout (); + Application.Current.SetNeedsDraw (); break; @@ -1216,7 +1216,7 @@ public class DialogTests (ITestOutputHelper output) └───────────────────────┘", output); - Assert.False (Application.Top!.NewKeyDownEvent (Key.Enter)); + Assert.False (Application.Current!.NewKeyDownEvent (Key.Enter)); break; case 7: @@ -1410,9 +1410,9 @@ public class DialogTests (ITestOutputHelper output) #if DEBUG_IDISPOSABLE Assert.False (dlg.WasDisposed); - Assert.False (Application.Top!.WasDisposed); - Assert.NotEqual (top, Application.Top); - Assert.Equal (dlg, Application.Top); + Assert.False (Application.Current!.WasDisposed); + Assert.NotEqual (top, Application.Current); + Assert.Equal (dlg, Application.Current); #endif // dlg wasn't disposed yet and it's possible to access to his properties @@ -1426,11 +1426,11 @@ public class DialogTests (ITestOutputHelper output) top.Dispose (); #if DEBUG_IDISPOSABLE Assert.True (dlg.WasDisposed); - Assert.True (Application.Top.WasDisposed); - Assert.NotNull (Application.Top); + Assert.True (Application.Current.WasDisposed); + Assert.NotNull (Application.Current); #endif Application.Shutdown (); - Assert.Null (Application.Top); + Assert.Null (Application.Current); return; diff --git a/Tests/UnitTests/Dialogs/MessageBoxTests.cs b/Tests/UnitTests/Dialogs/MessageBoxTests.cs index 075067f5f..0699309b7 100644 --- a/Tests/UnitTests/Dialogs/MessageBoxTests.cs +++ b/Tests/UnitTests/Dialogs/MessageBoxTests.cs @@ -193,7 +193,7 @@ public class MessageBoxTests (ITestOutputHelper output) } else if (iterations == 1) { - mbFrame = Application.Top!.Frame; + mbFrame = Application.Current!.Frame; Application.RequestStop (); } } @@ -378,8 +378,8 @@ public class MessageBoxTests (ITestOutputHelper output) { AutoInitShutdownAttribute.RunIteration (); - Assert.IsType (Application.Top); - Assert.Equal (new (height, width), Application.Top.Frame.Size); + Assert.IsType (Application.Current); + Assert.Equal (new (height, width), Application.Current.Frame.Size); Application.RequestStop (); } @@ -415,8 +415,8 @@ public class MessageBoxTests (ITestOutputHelper output) { AutoInitShutdownAttribute.RunIteration (); - Assert.IsType (Application.Top); - Assert.Equal (new (height, width), Application.Top.Frame.Size); + Assert.IsType (Application.Current); + Assert.Equal (new (height, width), Application.Current.Frame.Size); Application.RequestStop (); } @@ -448,8 +448,8 @@ public class MessageBoxTests (ITestOutputHelper output) { AutoInitShutdownAttribute.RunIteration (); - Assert.IsType (Application.Top); - Assert.Equal (new (height, width), Application.Top.Frame.Size); + Assert.IsType (Application.Current); + Assert.Equal (new (height, width), Application.Current.Frame.Size); Application.RequestStop (); } diff --git a/Tests/UnitTests/DriverAssert.cs b/Tests/UnitTests/DriverAssert.cs index a1abc36b5..4e7795b13 100644 --- a/Tests/UnitTests/DriverAssert.cs +++ b/Tests/UnitTests/DriverAssert.cs @@ -60,7 +60,7 @@ internal partial class DriverAssert { case 0: output.WriteLine ( - $"{Application.ToString (driver)}\n" + $"{driver.ToString ()}\n" + $"Expected Attribute {val} at Contents[{line},{c}] {contents [line, c]} was not found.\n" + $" Expected: {string.Join (",", expectedAttributes.Select (attr => attr))}\n" + $" But Was: " @@ -79,7 +79,7 @@ internal partial class DriverAssert if (colorUsed != userExpected) { - output.WriteLine ($"{Application.ToString (driver)}"); + output.WriteLine ($"{driver.ToString ()}"); output.WriteLine ($"Unexpected Attribute at Contents[{line},{c}] = {contents [line, c]}."); output.WriteLine ($" Expected: {userExpected} ({expectedAttributes [int.Parse (userExpected.ToString ())]})"); output.WriteLine ($" But Was: {colorUsed} ({val})"); @@ -152,7 +152,9 @@ internal partial class DriverAssert ) { #pragma warning restore xUnit1013 // Public method should be marked as test - var actualLook = Application.ToString (driver ?? Application.Driver); + driver ??= Application.Driver!; + + var actualLook = driver.ToString (); if (string.Equals (expectedLook, actualLook)) { @@ -198,7 +200,7 @@ internal partial class DriverAssert { List> lines = []; var sb = new StringBuilder (); - driver ??= Application.Driver; + driver ??= Application.Driver!; int x = -1; int y = -1; diff --git a/Tests/UnitTests/Drivers/ClipRegionTests.cs b/Tests/UnitTests/Drivers/ClipRegionTests.cs index 8ede58565..013cdbe7a 100644 --- a/Tests/UnitTests/Drivers/ClipRegionTests.cs +++ b/Tests/UnitTests/Drivers/ClipRegionTests.cs @@ -12,7 +12,7 @@ public class ClipRegionTests (ITestOutputHelper output) [Fact] public void AddRune_Is_Clipped () { - Application.Init (null, "fake"); + Application.Init ("fake"); Application.Driver!.Move (0, 0); Application.Driver!.AddRune ('x'); @@ -41,7 +41,7 @@ public class ClipRegionTests (ITestOutputHelper output) [Fact] public void Clip_Set_To_Empty_AllInvalid () { - Application.Init (null, "fake"); + Application.Init ("fake"); // Define a clip rectangle Application.Driver!.Clip = new (Rectangle.Empty); @@ -64,7 +64,7 @@ public class ClipRegionTests (ITestOutputHelper output) [Fact] public void IsValidLocation () { - Application.Init (null, "fake"); + Application.Init ("fake"); Application.Driver!.Rows = 10; Application.Driver!.Cols = 10; diff --git a/Tests/UnitTests/Drivers/DriverTests.cs b/Tests/UnitTests/Drivers/DriverTests.cs index 558245c9e..202ec7208 100644 --- a/Tests/UnitTests/Drivers/DriverTests.cs +++ b/Tests/UnitTests/Drivers/DriverTests.cs @@ -1,5 +1,4 @@ -using System.Text; -using UnitTests.ViewsTests; +#nullable enable using Xunit.Abstractions; namespace UnitTests.DriverTests; @@ -8,16 +7,16 @@ public class DriverTests (ITestOutputHelper output) { private readonly ITestOutputHelper _output = output; - [Theory] [InlineData ("fake")] [InlineData ("windows")] [InlineData ("dotnet")] [InlineData ("unix")] - public void All_Drivers_Init_Shutdown_Cross_Platform (string driverName = null) + public void All_Drivers_Init_Shutdown_Cross_Platform (string driverName) { - Application.Init (null, driverName: driverName); - Application.Shutdown (); + IApplication? app = Application.Create (); + app.Init (driverName); + app.Shutdown (); } [Theory] @@ -25,12 +24,13 @@ public class DriverTests (ITestOutputHelper output) [InlineData ("windows")] [InlineData ("dotnet")] [InlineData ("unix")] - public void All_Drivers_Run_Cross_Platform (string driverName = null) + public void All_Drivers_Run_Cross_Platform (string driverName) { - Application.Init (null, driverName: driverName); - Application.StopAfterFirstIteration = true; - Application.Run ().Dispose (); - Application.Shutdown (); + IApplication? app = Application.Create (); + app.Init (driverName); + app.StopAfterFirstIteration = true; + app.Run ().Dispose (); + app.Shutdown (); } [Theory] @@ -38,22 +38,22 @@ public class DriverTests (ITestOutputHelper output) [InlineData ("windows")] [InlineData ("dotnet")] [InlineData ("unix")] - public void All_Drivers_LayoutAndDraw_Cross_Platform (string driverName = null) + public void All_Drivers_LayoutAndDraw_Cross_Platform (string driverName) { - Application.Init (null, driverName: driverName); - Application.StopAfterFirstIteration = true; - Application.Run ().Dispose (); + IApplication? app = Application.Create (); + app.Init (driverName); + app.StopAfterFirstIteration = true; + app.Run ().Dispose (); - DriverAssert.AssertDriverContentsWithFrameAre (expectedLook: driverName!, _output); - - Application.Shutdown (); + DriverAssert.AssertDriverContentsWithFrameAre (driverName!, _output, app.Driver); + app.Shutdown (); } } public class TestTop : Toplevel { - /// + /// public override void BeginInit () { Text = Driver!.GetName ()!; diff --git a/Tests/UnitTests/SetupFakeApplicationAttribute.cs b/Tests/UnitTests/SetupFakeApplicationAttribute.cs index 930e44c41..0b8633da7 100644 --- a/Tests/UnitTests/SetupFakeApplicationAttribute.cs +++ b/Tests/UnitTests/SetupFakeApplicationAttribute.cs @@ -32,6 +32,7 @@ public class SetupFakeApplicationAttribute : BeforeAfterTestAttribute _appDispose?.Dispose (); _appDispose = null; + ApplicationImpl.SetInstance (null); base.After (methodUnderTest); } diff --git a/Tests/UnitTests/View/Adornment/AdornmentSubViewTests.cs b/Tests/UnitTests/View/Adornment/AdornmentSubViewTests.cs index 52d5bc01e..7bfa4747a 100644 --- a/Tests/UnitTests/View/Adornment/AdornmentSubViewTests.cs +++ b/Tests/UnitTests/View/Adornment/AdornmentSubViewTests.cs @@ -14,14 +14,14 @@ public class AdornmentSubViewTests (ITestOutputHelper output) [InlineData (2, 1, true)] public void Adornment_WithSubView_Finds (int viewMargin, int subViewMargin, bool expectedFound) { - Application.Top = new Toplevel() + Application.Current = new Toplevel() { Width = 10, Height = 10 }; - Application.Top.Margin!.Thickness = new Thickness (viewMargin); + Application.Current.Margin!.Thickness = new Thickness (viewMargin); // Turn of TransparentMouse for the test - Application.Top.Margin!.ViewportSettings = ViewportSettingsFlags.None; + Application.Current.Margin!.ViewportSettings = ViewportSettingsFlags.None; var subView = new View () { @@ -34,26 +34,26 @@ public class AdornmentSubViewTests (ITestOutputHelper output) // Turn of TransparentMouse for the test subView.Margin!.ViewportSettings = ViewportSettingsFlags.None; - Application.Top.Margin!.Add (subView); - Application.Top.Layout (); + Application.Current.Margin!.Add (subView); + Application.Current.Layout (); - var foundView = View.GetViewsUnderLocation (new Point(0, 0), ViewportSettingsFlags.None).LastOrDefault (); + var foundView = Application.Current.GetViewsUnderLocation (new Point(0, 0), ViewportSettingsFlags.None).LastOrDefault (); bool found = foundView == subView || foundView == subView.Margin; Assert.Equal (expectedFound, found); - Application.Top.Dispose (); + Application.Current.Dispose (); Application.ResetState (ignoreDisposed: true); } [Fact] public void Adornment_WithNonVisibleSubView_Finds_Adornment () { - Application.Top = new Toplevel () + Application.Current = new Toplevel () { Width = 10, Height = 10 }; - Application.Top.Padding.Thickness = new Thickness (1); + Application.Current.Padding.Thickness = new Thickness (1); var subView = new View () { @@ -63,11 +63,11 @@ public class AdornmentSubViewTests (ITestOutputHelper output) Height = 1, Visible = false }; - Application.Top.Padding.Add (subView); - Application.Top.Layout (); + Application.Current.Padding.Add (subView); + Application.Current.Layout (); - Assert.Equal (Application.Top.Padding, View.GetViewsUnderLocation (new Point(0, 0), ViewportSettingsFlags.None).LastOrDefault ()); - Application.Top?.Dispose (); + Assert.Equal (Application.Current.Padding, Application.Current.GetViewsUnderLocation (new Point(0, 0), ViewportSettingsFlags.None).LastOrDefault ()); + Application.Current?.Dispose (); Application.ResetState (ignoreDisposed: true); } } diff --git a/Tests/UnitTests/View/Adornment/AdornmentTests.cs b/Tests/UnitTests/View/Adornment/AdornmentTests.cs index 176dbc719..3ce9f3b5e 100644 --- a/Tests/UnitTests/View/Adornment/AdornmentTests.cs +++ b/Tests/UnitTests/View/Adornment/AdornmentTests.cs @@ -9,7 +9,11 @@ public class AdornmentTests (ITestOutputHelper output) [SetupFakeApplication] public void Border_Is_Cleared_After_Margin_Thickness_Change () { - View view = new () { Text = "View", Width = 6, Height = 3, BorderStyle = LineStyle.Rounded }; + View view = new () + { + App = ApplicationImpl.Instance, + Text = "View", Width = 6, Height = 3, BorderStyle = LineStyle.Rounded + }; // Remove border bottom thickness view.Border!.Thickness = new (1, 1, 1, 0); @@ -59,7 +63,7 @@ public class AdornmentTests (ITestOutputHelper output) Assert.Equal (6, view.Width); Assert.Equal (3, view.Height); - View.SetClipToScreen (); + view.SetClipToScreen (); view.Draw (); DriverAssert.AssertDriverContentsWithFrameAre ( diff --git a/Tests/UnitTests/View/Adornment/BorderTests.cs b/Tests/UnitTests/View/Adornment/BorderTests.cs index 80a769258..e7f02752d 100644 --- a/Tests/UnitTests/View/Adornment/BorderTests.cs +++ b/Tests/UnitTests/View/Adornment/BorderTests.cs @@ -8,7 +8,11 @@ public class BorderTests (ITestOutputHelper output) [SetupFakeApplication] public void Border_Parent_HasFocus_Title_Uses_FocusAttribute () { - var superView = new View { Width = 10, Height = 10, CanFocus = true }; + var superView = new View + { + Driver = ApplicationImpl.Instance.Driver, + Width = 10, Height = 10, CanFocus = true + }; var otherView = new View { Width = 0, Height = 0, CanFocus = true }; superView.Add (otherView); @@ -39,7 +43,7 @@ public class BorderTests (ITestOutputHelper output) view.CanFocus = true; view.SetFocus (); - View.SetClipToScreen (); + view.SetClipToScreen (); view.Draw (); Assert.Equal (view.GetAttributeForRole (VisualRole.Focus), view.Border!.GetAttributeForRole (VisualRole.Focus)); Assert.Equal (view.GetScheme ().Focus.Foreground, view.Border!.GetAttributeForRole (VisualRole.Focus).Foreground); @@ -51,7 +55,11 @@ public class BorderTests (ITestOutputHelper output) [SetupFakeApplication] public void Border_Uses_Parent_Scheme () { - var view = new View { Title = "A", Height = 2, Width = 5 }; + var view = new View + { + Driver = ApplicationImpl.Instance.Driver, + Title = "A", Height = 2, Width = 5 + }; view.Border!.Thickness = new (0, 1, 0, 0); view.Border!.LineStyle = LineStyle.Single; @@ -839,6 +847,7 @@ public class BorderTests (ITestOutputHelper output) { var superView = new View { + Driver = ApplicationImpl.Instance.Driver, Id = "superView", Width = 5, Height = 5, @@ -902,6 +911,7 @@ public class BorderTests (ITestOutputHelper output) { var superView = new View { + Driver = ApplicationImpl.Instance.Driver, Id = "superView", Title = "A", Width = 11, diff --git a/Tests/UnitTests/View/Adornment/MarginTests.cs b/Tests/UnitTests/View/Adornment/MarginTests.cs index 82053e1ef..3a4bc20ba 100644 --- a/Tests/UnitTests/View/Adornment/MarginTests.cs +++ b/Tests/UnitTests/View/Adornment/MarginTests.cs @@ -15,27 +15,27 @@ public class MarginTests (ITestOutputHelper output) view.Margin!.Diagnostics = ViewDiagnosticFlags.Thickness; view.Margin.Thickness = new (1); - Application.Top = new Toplevel (); - Application.TopLevels.Push (Application.Top); + Application.Current = new Toplevel (); + Application.SessionStack.Push (Application.Current); - Application.Top.SetScheme (new() + Application.Current.SetScheme (new() { Normal = new (Color.Red, Color.Green), Focus = new (Color.Green, Color.Red) }); - Application.Top.Add (view); + Application.Current.Add (view); Assert.Equal (ColorName16.Red, view.Margin.GetAttributeForRole (VisualRole.Normal).Foreground.GetClosestNamedColor16 ()); - Assert.Equal (ColorName16.Red, Application.Top.GetAttributeForRole (VisualRole.Normal).Foreground.GetClosestNamedColor16 ()); + Assert.Equal (ColorName16.Red, Application.Current.GetAttributeForRole (VisualRole.Normal).Foreground.GetClosestNamedColor16 ()); - Application.Top.BeginInit (); - Application.Top.EndInit (); + Application.Current.BeginInit (); + Application.Current.EndInit (); Application.LayoutAndDraw(); DriverAssert.AssertDriverContentsAre ( @"", output ); - DriverAssert.AssertDriverAttributesAre ("0", output, null, Application.Top.GetAttributeForRole (VisualRole.Normal)); + DriverAssert.AssertDriverAttributesAre ("0", output, null, Application.Current.GetAttributeForRole (VisualRole.Normal)); Application.ResetState (true); } @@ -51,20 +51,20 @@ public class MarginTests (ITestOutputHelper output) view.Margin.Thickness = new (1); view.Margin.ViewportSettings = ViewportSettingsFlags.None; - Application.Top = new Toplevel (); - Application.TopLevels.Push (Application.Top); + Application.Current = new Toplevel (); + Application.SessionStack.Push (Application.Current); - Application.Top.SetScheme (new () + Application.Current.SetScheme (new () { Normal = new (Color.Red, Color.Green), Focus = new (Color.Green, Color.Red) }); - Application.Top.Add (view); + Application.Current.Add (view); Assert.Equal (ColorName16.Red, view.Margin.GetAttributeForRole (VisualRole.Normal).Foreground.GetClosestNamedColor16 ()); - Assert.Equal (ColorName16.Red, Application.Top.GetAttributeForRole (VisualRole.Normal).Foreground.GetClosestNamedColor16 ()); + Assert.Equal (ColorName16.Red, Application.Current.GetAttributeForRole (VisualRole.Normal).Foreground.GetClosestNamedColor16 ()); - Application.Top.BeginInit (); - Application.Top.EndInit (); + Application.Current.BeginInit (); + Application.Current.EndInit (); Application.LayoutAndDraw (); DriverAssert.AssertDriverContentsAre ( @@ -74,7 +74,7 @@ M M MMM", output ); - DriverAssert.AssertDriverAttributesAre ("0", output, null, Application.Top.GetAttributeForRole (VisualRole.Normal)); + DriverAssert.AssertDriverAttributesAre ("0", output, null, Application.Current.GetAttributeForRole (VisualRole.Normal)); Application.ResetState (true); } diff --git a/Tests/UnitTests/View/Adornment/PaddingTests.cs b/Tests/UnitTests/View/Adornment/PaddingTests.cs index d869a8d26..f48191350 100644 --- a/Tests/UnitTests/View/Adornment/PaddingTests.cs +++ b/Tests/UnitTests/View/Adornment/PaddingTests.cs @@ -9,8 +9,12 @@ public class PaddingTests (ITestOutputHelper output) [SetupFakeApplication] public void Padding_Uses_Parent_Scheme () { - Application.Driver!.SetScreenSize (5, 5); - var view = new View { Height = 3, Width = 3 }; + ApplicationImpl.Instance.Driver!.SetScreenSize (5, 5); + var view = new View + { + App = ApplicationImpl.Instance, + Height = 3, Width = 3 + }; view.Padding!.Thickness = new (1); view.Padding.Diagnostics = ViewDiagnosticFlags.Thickness; diff --git a/Tests/UnitTests/View/Adornment/ShadowStyleTests.cs b/Tests/UnitTests/View/Adornment/ShadowStyleTests.cs index d640a314c..48f24476c 100644 --- a/Tests/UnitTests/View/Adornment/ShadowStyleTests.cs +++ b/Tests/UnitTests/View/Adornment/ShadowStyleTests.cs @@ -48,6 +48,7 @@ public class ShadowStyleTests (ITestOutputHelper output) var superView = new Toplevel { + Driver = ApplicationImpl.Instance.Driver, Height = 3, Width = 3, Text = "012ABC!@#", @@ -65,7 +66,7 @@ public class ShadowStyleTests (ITestOutputHelper output) view.SetScheme (new (Attribute.Default)); superView.Add (view); - Application.TopLevels.Push (superView); + Application.SessionStack.Push (superView); Application.LayoutAndDraw (true); DriverAssert.AssertDriverAttributesAre (expectedAttrs, output, Application.Driver, attributes); Application.ResetState (true); @@ -104,6 +105,7 @@ public class ShadowStyleTests (ITestOutputHelper output) var superView = new Toplevel { + Driver = ApplicationImpl.Instance.Driver, Width = 4, Height = 4, Text = "!@#$".Repeat (4)! @@ -118,7 +120,7 @@ public class ShadowStyleTests (ITestOutputHelper output) }; view.ShadowStyle = style; superView.Add (view); - Application.TopLevels.Push (superView); + Application.SessionStack.Push (superView); Application.LayoutAndDraw (true); DriverAssert.AssertDriverContentsWithFrameAre (expected, output); @@ -136,7 +138,8 @@ public class ShadowStyleTests (ITestOutputHelper output) { var superView = new View { - Height = 10, Width = 10 + Height = 10, Width = 10, + App = ApplicationImpl.Instance }; View view = new () diff --git a/Tests/UnitTests/View/ArrangementTests.cs b/Tests/UnitTests/View/ArrangementTests.cs new file mode 100644 index 000000000..13e5d880a --- /dev/null +++ b/Tests/UnitTests/View/ArrangementTests.cs @@ -0,0 +1,217 @@ +using Xunit.Abstractions; + +namespace UnitTests.ViewTests; + +public class ArrangementTests (ITestOutputHelper output) +{ + private readonly ITestOutputHelper _output = output; + + [Fact] + public void MouseGrabHandler_WorksWithMovableView_UsingNewMouseEvent () + { + // This test proves that MouseGrabHandler works correctly with concurrent unit tests + // using NewMouseEvent directly on views, without requiring Application.Init + + var superView = new View + { + Width = 80, + Height = 25 + }; + superView.App = Application.Create (); + + var movableView = new View + { + Arrangement = ViewArrangement.Movable, + BorderStyle = LineStyle.Single, + X = 10, + Y = 10, + Width = 20, + Height = 10 + }; + + superView.Add (movableView); + + // Verify initial state + Assert.NotNull (movableView.Border); + Assert.Null (Application.Mouse.MouseGrabView); + + // Simulate mouse press on the border to start dragging + var pressEvent = new MouseEventArgs + { + Position = new (1, 0), // Top border area + Flags = MouseFlags.Button1Pressed + }; + + bool? result = movableView.Border.NewMouseEvent (pressEvent); + + // The border should have grabbed the mouse + Assert.True (result); + Assert.Equal (movableView.Border, superView.App.Mouse.MouseGrabView); + + // Simulate mouse drag + var dragEvent = new MouseEventArgs + { + Position = new (5, 2), + Flags = MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition + }; + + result = movableView.Border.NewMouseEvent (dragEvent); + Assert.True (result); + + // Mouse should still be grabbed + Assert.Equal (movableView.Border, superView.App.Mouse.MouseGrabView); + + // Simulate mouse release to end dragging + var releaseEvent = new MouseEventArgs + { + Position = new (5, 2), + Flags = MouseFlags.Button1Released + }; + + result = movableView.Border.NewMouseEvent (releaseEvent); + Assert.True (result); + + // Mouse should be released + Assert.Null (superView.App.Mouse.MouseGrabView); + } + + [Fact] + public void MouseGrabHandler_WorksWithResizableView_UsingNewMouseEvent () + { + // This test proves MouseGrabHandler works for resizing operations + + var superView = new View + { + App = Application.Create (), + Width = 80, + Height = 25 + }; + + var resizableView = new View + { + Arrangement = ViewArrangement.RightResizable, + BorderStyle = LineStyle.Single, + X = 10, + Y = 10, + Width = 20, + Height = 10 + }; + + superView.Add (resizableView); + + // Verify initial state + Assert.NotNull (resizableView.Border); + Assert.Null (Application.Mouse.MouseGrabView); + + // Calculate position on right border (border is at right edge) + // Border.Frame.X is relative to parent, so we use coordinates relative to the border + var pressEvent = new MouseEventArgs + { + Position = new (resizableView.Border.Frame.Width - 1, 5), // Right border area + Flags = MouseFlags.Button1Pressed + }; + + bool? result = resizableView.Border.NewMouseEvent (pressEvent); + + // The border should have grabbed the mouse for resizing + Assert.True (result); + Assert.Equal (resizableView.Border, superView.App.Mouse.MouseGrabView); + + // Simulate dragging to resize + var dragEvent = new MouseEventArgs + { + Position = new (resizableView.Border.Frame.Width + 3, 5), + Flags = MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition + }; + + result = resizableView.Border.NewMouseEvent (dragEvent); + Assert.True (result); + Assert.Equal (resizableView.Border, superView.App.Mouse.MouseGrabView); + + // Simulate mouse release + var releaseEvent = new MouseEventArgs + { + Position = new (resizableView.Border.Frame.Width + 3, 5), + Flags = MouseFlags.Button1Released + }; + + result = resizableView.Border.NewMouseEvent (releaseEvent); + Assert.True (result); + + // Mouse should be released + Assert.Null (superView.App.Mouse.MouseGrabView); + } + + [Fact] + public void MouseGrabHandler_ReleasesOnMultipleViews () + { + // This test verifies MouseGrabHandler properly releases when switching between views + + var superView = new View { Width = 80, Height = 25 }; + superView.App = Application.Create (); + + var view1 = new View + { + Arrangement = ViewArrangement.Movable, + BorderStyle = LineStyle.Single, + X = 10, + Y = 10, + Width = 15, + Height = 8 + }; + + var view2 = new View + { + Arrangement = ViewArrangement.Movable, + BorderStyle = LineStyle.Single, + X = 30, + Y = 10, + Width = 15, + Height = 8 + }; + + superView.Add (view1, view2); + superView.BeginInit (); + superView.EndInit (); + + // Grab mouse on first view + var pressEvent1 = new MouseEventArgs + { + Position = new (1, 0), + Flags = MouseFlags.Button1Pressed + }; + + view1.Border!.NewMouseEvent (pressEvent1); + Assert.Equal (view1.Border, superView.App.Mouse.MouseGrabView); + + // Release on first view + var releaseEvent1 = new MouseEventArgs + { + Position = new (1, 0), + Flags = MouseFlags.Button1Released + }; + + view1.Border.NewMouseEvent (releaseEvent1); + Assert.Null (Application.Mouse.MouseGrabView); + + // Grab mouse on second view + var pressEvent2 = new MouseEventArgs + { + Position = new (1, 0), + Flags = MouseFlags.Button1Pressed + }; + + view2.Border!.NewMouseEvent (pressEvent2); + Assert.Equal (view2.Border, superView.App.Mouse.MouseGrabView); + + // Release on second view + var releaseEvent2 = new MouseEventArgs + { + Position = new (1, 0), + Flags = MouseFlags.Button1Released + }; + + view2.Border.NewMouseEvent (releaseEvent2); + Assert.Null (superView.App.Mouse.MouseGrabView); + } +} diff --git a/Tests/UnitTests/View/Draw/ClearViewportTests.cs b/Tests/UnitTests/View/Draw/ClearViewportTests.cs index 3cdcc9401..db64eb72d 100644 --- a/Tests/UnitTests/View/Draw/ClearViewportTests.cs +++ b/Tests/UnitTests/View/Draw/ClearViewportTests.cs @@ -101,7 +101,11 @@ public class ClearViewportTests (ITestOutputHelper output) [SetupFakeApplication] public void Clear_ClearsEntireViewport () { - var superView = new View { Width = Dim.Fill (), Height = Dim.Fill () }; + var superView = new View + { + App = ApplicationImpl.Instance, + Width = Dim.Fill (), Height = Dim.Fill () + }; var view = new View { @@ -133,7 +137,7 @@ public class ClearViewportTests (ITestOutputHelper output) └─┘", output); - View.SetClipToScreen (); + view.SetClipToScreen (); view.ClearViewport (); @@ -149,7 +153,11 @@ public class ClearViewportTests (ITestOutputHelper output) [SetupFakeApplication] public void Clear_WithClearVisibleContentOnly_ClearsVisibleContentOnly () { - var superView = new View { Width = Dim.Fill (), Height = Dim.Fill () }; + var superView = new View + { + App = ApplicationImpl.Instance, + Width = Dim.Fill (), Height = Dim.Fill () + }; var view = new View { @@ -172,7 +180,7 @@ public class ClearViewportTests (ITestOutputHelper output) │X│ └─┘", output); - View.SetClipToScreen (); + view.SetClipToScreen (); view.ClearViewport (); DriverAssert.AssertDriverContentsWithFrameAre ( @@ -203,7 +211,7 @@ public class ClearViewportTests (ITestOutputHelper output) } } - View.SetClip (savedClip); + view.SetClip (savedClip); e.Cancel = true; }; var top = new Toplevel (); @@ -268,7 +276,7 @@ public class ClearViewportTests (ITestOutputHelper output) } } - View.SetClip (savedClip); + view.SetClip (savedClip); e.Cancel = true; }; var top = new Toplevel (); diff --git a/Tests/UnitTests/View/Draw/ClipTests.cs b/Tests/UnitTests/View/Draw/ClipTests.cs index 44b120d76..4e3f62e60 100644 --- a/Tests/UnitTests/View/Draw/ClipTests.cs +++ b/Tests/UnitTests/View/Draw/ClipTests.cs @@ -14,6 +14,7 @@ public class ClipTests (ITestOutputHelper _output) { var view = new View { + App = ApplicationImpl.Instance, X = 1, Y = 1, Width = 3, Height = 3 @@ -36,6 +37,7 @@ public class ClipTests (ITestOutputHelper _output) { var view = new View { + App = ApplicationImpl.Instance, X = 1, Y = 1, Width = 3, Height = 3 @@ -67,7 +69,11 @@ public class ClipTests (ITestOutputHelper _output) [SetupFakeApplication] public void FillRect_Fills_HonorsClip (int x, int y, int width, int height) { - var superView = new View { Width = Dim.Fill (), Height = Dim.Fill () }; + var superView = new View + { + App = ApplicationImpl.Instance, + Width = Dim.Fill (), Height = Dim.Fill () + }; var view = new View { @@ -91,7 +97,7 @@ public class ClipTests (ITestOutputHelper _output) _output); Rectangle toFill = new (x, y, width, height); - View.SetClipToScreen (); + superView.SetClipToScreen (); view.FillRect (toFill); DriverAssert.AssertDriverContentsWithFrameAre ( @@ -133,7 +139,7 @@ public class ClipTests (ITestOutputHelper _output) _output); toFill = new (-1, -1, width + 1, height + 1); - View.SetClipToScreen (); + superView.SetClipToScreen (); view.FillRect (toFill); DriverAssert.AssertDriverContentsWithFrameAre ( @@ -154,7 +160,7 @@ public class ClipTests (ITestOutputHelper _output) └─┘", _output); toFill = new (0, 0, width * 2, height * 2); - View.SetClipToScreen (); + superView.SetClipToScreen (); view.FillRect (toFill); DriverAssert.AssertDriverContentsWithFrameAre ( @@ -175,6 +181,7 @@ public class ClipTests (ITestOutputHelper _output) var top = new View { + App = ApplicationImpl.Instance, Id = "top", Width = Dim.Fill (), Height = Dim.Fill () @@ -193,7 +200,7 @@ public class ClipTests (ITestOutputHelper _output) frameView.Border!.Thickness = new (1, 0, 0, 0); top.Add (frameView); - View.SetClipToScreen (); + top.SetClipToScreen (); top.Layout (); top.Draw (); @@ -217,7 +224,7 @@ public class ClipTests (ITestOutputHelper _output) top.Add (view); top.Layout (); - View.SetClipToScreen (); + top.SetClipToScreen (); top.Draw (); // 012345678901234567890123456789012345678 @@ -252,19 +259,20 @@ public class ClipTests (ITestOutputHelper _output) { Width = Dim.Fill (), Height = Dim.Fill (), - ViewportSettings = ViewportSettingsFlags.ClipContentOnly + ViewportSettings = ViewportSettingsFlags.ClipContentOnly, + App = ApplicationImpl.Instance }; view.SetContentSize (new Size (10, 10)); view.Border!.Thickness = new (1); view.BeginInit (); view.EndInit (); - Assert.Equal (view.Frame, View.GetClip ()!.GetBounds ()); + Assert.Equal (view.Frame, view.GetClip ()!.GetBounds ()); // Act view.AddViewportToClip (); // Assert - Assert.Equal (expectedClip, View.GetClip ()!.GetBounds ()); + Assert.Equal (expectedClip, view.GetClip ()!.GetBounds ()); view.Dispose (); } @@ -286,20 +294,21 @@ public class ClipTests (ITestOutputHelper _output) var view = new View { Width = Dim.Fill (), - Height = Dim.Fill () + Height = Dim.Fill (), + App = ApplicationImpl.Instance }; view.SetContentSize (new Size (10, 10)); view.Border!.Thickness = new (1); view.BeginInit (); view.EndInit (); - Assert.Equal (view.Frame, View.GetClip ()!.GetBounds ()); + Assert.Equal (view.Frame, view.GetClip ()!.GetBounds ()); view.Viewport = view.Viewport with { X = 1, Y = 1 }; // Act view.AddViewportToClip (); // Assert - Assert.Equal (expectedClip, View.GetClip ()!.GetBounds ()); + Assert.Equal (expectedClip, view.GetClip ()!.GetBounds ()); view.Dispose (); } } diff --git a/Tests/UnitTests/View/Draw/DrawTests.cs b/Tests/UnitTests/View/Draw/DrawTests.cs index 2526c2178..0e84371d5 100644 --- a/Tests/UnitTests/View/Draw/DrawTests.cs +++ b/Tests/UnitTests/View/Draw/DrawTests.cs @@ -114,7 +114,11 @@ public class DrawTests (ITestOutputHelper output) [SetupFakeApplication] public void Draw_Minimum_Full_Border_With_Empty_Viewport () { - var view = new View { Width = 2, Height = 2, BorderStyle = LineStyle.Single }; + var view = new View + { + App = ApplicationImpl.Instance, + Width = 2, Height = 2, BorderStyle = LineStyle.Single + }; Assert.True (view.NeedsLayout); Assert.True (view.NeedsDraw); view.Layout (); @@ -139,7 +143,11 @@ public class DrawTests (ITestOutputHelper output) [SetupFakeApplication] public void Draw_Minimum_Full_Border_With_Empty_Viewport_Without_Bottom () { - var view = new View { Width = 2, Height = 1, BorderStyle = LineStyle.Single }; + var view = new View + { + App = ApplicationImpl.Instance, + Width = 2, Height = 1, BorderStyle = LineStyle.Single + }; view.Border!.Thickness = new (1, 1, 1, 0); view.BeginInit (); view.EndInit (); @@ -157,7 +165,11 @@ public class DrawTests (ITestOutputHelper output) [SetupFakeApplication] public void Draw_Minimum_Full_Border_With_Empty_Viewport_Without_Left () { - var view = new View { Width = 1, Height = 2, BorderStyle = LineStyle.Single }; + var view = new View + { + App = ApplicationImpl.Instance, + Width = 1, Height = 2, BorderStyle = LineStyle.Single + }; view.Border!.Thickness = new (0, 1, 1, 1); view.BeginInit (); view.EndInit (); @@ -182,7 +194,11 @@ public class DrawTests (ITestOutputHelper output) [SetupFakeApplication] public void Draw_Minimum_Full_Border_With_Empty_Viewport_Without_Right () { - var view = new View { Width = 1, Height = 2, BorderStyle = LineStyle.Single }; + var view = new View + { + App = ApplicationImpl.Instance, + Width = 1, Height = 2, BorderStyle = LineStyle.Single + }; view.Border!.Thickness = new (1, 1, 0, 1); view.BeginInit (); view.EndInit (); @@ -207,7 +223,11 @@ public class DrawTests (ITestOutputHelper output) [SetupFakeApplication] public void Draw_Minimum_Full_Border_With_Empty_Viewport_Without_Top () { - var view = new View { Width = 2, Height = 1, BorderStyle = LineStyle.Single }; + var view = new View + { + App = ApplicationImpl.Instance, + Width = 2, Height = 1, BorderStyle = LineStyle.Single + }; view.Border!.Thickness = new (1, 0, 1, 1); view.BeginInit (); @@ -587,7 +607,11 @@ public class DrawTests (ITestOutputHelper output) [InlineData ("a𐐀b")] public void DrawHotString_NonBmp (string expected) { - var view = new View { Width = 10, Height = 1 }; + var view = new View + { + App = ApplicationImpl.Instance, + Width = 10, Height = 1 + }; view.DrawHotString (expected, Attribute.Default, Attribute.Default); DriverAssert.AssertDriverContentsWithFrameAre (expected, output); @@ -760,7 +784,7 @@ At 0,0 Assert.Equal (new (3, 3, 10, 1), view.Frame); Assert.Equal (new (0, 0, 10, 1), view.Viewport); Assert.Equal (new (0, 0, 10, 1), view.NeedsDrawRect); - View.SetClipToScreen (); + view.SetClipToScreen (); top.Draw (); DriverAssert.AssertDriverContentsWithFrameAre ( @@ -859,7 +883,7 @@ At 0,0 Assert.Equal (new (1, 1, 10, 1), view.Frame); Assert.Equal (new (0, 0, 10, 1), view.Viewport); Assert.Equal (new (0, 0, 10, 1), view.NeedsDrawRect); - View.SetClipToScreen (); + view.SetClipToScreen (); top.Draw (); diff --git a/Tests/UnitTests/View/Draw/TransparentTests.cs b/Tests/UnitTests/View/Draw/TransparentTests.cs index 6a38f91e7..050ae18cb 100644 --- a/Tests/UnitTests/View/Draw/TransparentTests.cs +++ b/Tests/UnitTests/View/Draw/TransparentTests.cs @@ -14,6 +14,7 @@ public class TransparentTests (ITestOutputHelper output) { var super = new View { + App = ApplicationImpl.Instance, Id = "super", Width = 20, Height = 5, @@ -58,6 +59,7 @@ public class TransparentTests (ITestOutputHelper output) { var super = new View { + App = ApplicationImpl.Instance, Id = "super", Width = 20, Height = 5, diff --git a/Tests/UnitTests/View/Keyboard/KeyBindingsTests.cs b/Tests/UnitTests/View/Keyboard/KeyBindingsTests.cs index 74bfebcbe..236ba0600 100644 --- a/Tests/UnitTests/View/Keyboard/KeyBindingsTests.cs +++ b/Tests/UnitTests/View/Keyboard/KeyBindingsTests.cs @@ -160,14 +160,14 @@ public class KeyBindingsTests () var hotKeyRaised = false; var acceptRaised = false; var selectRaised = false; - Application.Top = new Toplevel (); + Application.Current = new Toplevel (); var view = new View { CanFocus = true, HotKeySpecifier = new Rune ('_'), Title = "_Test" }; - Application.Top.Add (view); + Application.Current.Add (view); view.HandlingHotKey += (s, e) => hotKeyRaised = true; view.Accepting += (s, e) => acceptRaised = true; view.Selecting += (s, e) => selectRaised = true; @@ -191,7 +191,7 @@ public class KeyBindingsTests () Assert.False (acceptRaised); Assert.False (selectRaised); - Application.Top.Dispose (); + Application.Current.Dispose (); Application.ResetState (true); } // tests that test KeyBindingScope.Focus and KeyBindingScope.HotKey (tests for KeyBindingScope.Application are in Application/KeyboardTests.cs) diff --git a/Tests/UnitTests/View/Layout/GetViewsUnderLocationTests.cs b/Tests/UnitTests/View/Layout/GetViewsUnderLocationTests.cs index ec1fcdd08..97a4fc19d 100644 --- a/Tests/UnitTests/View/Layout/GetViewsUnderLocationTests.cs +++ b/Tests/UnitTests/View/Layout/GetViewsUnderLocationTests.cs @@ -70,22 +70,22 @@ public class GetViewsUnderLocationTests ) { // Arrange - Application.Top = new () + Application.Current = new () { Id = "Top", Frame = new (frameX, frameY, 10, 10) }; - Application.Top.Margin!.Thickness = new (marginThickness); - Application.Top.Margin!.Id = "Margin"; - Application.Top.Border!.Thickness = new (borderThickness); - Application.Top.Border!.Id = "Border"; - Application.Top.Padding!.Thickness = new (paddingThickness); - Application.Top.Padding.Id = "Padding"; + Application.Current.Margin!.Thickness = new (marginThickness); + Application.Current.Margin!.Id = "Margin"; + Application.Current.Border!.Thickness = new (borderThickness); + Application.Current.Border!.Id = "Border"; + Application.Current.Padding!.Thickness = new (paddingThickness); + Application.Current.Padding.Id = "Padding"; var location = new Point (testX, testY); // Act - List viewsUnderMouse = View.GetViewsUnderLocation (location, ViewportSettingsFlags.TransparentMouse); + List viewsUnderMouse = Application.Current.GetViewsUnderLocation (location, ViewportSettingsFlags.TransparentMouse); // Assert if (expectedViewsFound.Length == 0) @@ -98,7 +98,7 @@ public class GetViewsUnderLocationTests Assert.Equal (expectedViewsFound, foundIds); } - Application.Top.Dispose (); + Application.Current.Dispose (); Application.ResetState (true); } @@ -109,7 +109,7 @@ public class GetViewsUnderLocationTests public void Returns_Top_If_No_SubViews (int testX, int testY) { // Arrange - Application.Top = new () + Application.Current = new () { Frame = new (0, 0, 10, 10) }; @@ -117,11 +117,11 @@ public class GetViewsUnderLocationTests var location = new Point (testX, testY); // Act - List viewsUnderMouse = View.GetViewsUnderLocation (location, ViewportSettingsFlags.TransparentMouse); + List viewsUnderMouse = Application.Current.GetViewsUnderLocation (location, ViewportSettingsFlags.TransparentMouse); // Assert - Assert.Contains (viewsUnderMouse, v => v == Application.Top); - Application.Top.Dispose (); + Assert.Contains (viewsUnderMouse, v => v == Application.Current); + Application.Current.Dispose (); Application.ResetState (true); } @@ -134,13 +134,13 @@ public class GetViewsUnderLocationTests { Application.ResetState (true); - Application.Top = new () + Application.Current = new () { Width = 10, Height = 10 }; - Assert.Same (Application.Top, View.GetViewsUnderLocation (new (testX, testY), ViewportSettingsFlags.TransparentMouse).LastOrDefault ()); - Application.Top.Dispose (); + Assert.Same (Application.Current, Application.Current.GetViewsUnderLocation (new (testX, testY), ViewportSettingsFlags.TransparentMouse).LastOrDefault ()); + Application.Current.Dispose (); Application.ResetState (true); } @@ -155,7 +155,7 @@ public class GetViewsUnderLocationTests [InlineData (5, 6, true)] public void Returns_Correct_If_SubViews (int testX, int testY, bool expectedSubViewFound) { - Application.Top = new () + Application.Current = new () { Width = 10, Height = 10 }; @@ -165,12 +165,12 @@ public class GetViewsUnderLocationTests X = 1, Y = 2, Width = 5, Height = 5 }; - Application.Top.Add (subview); + Application.Current.Add (subview); - View? found = View.GetViewsUnderLocation (new (testX, testY), ViewportSettingsFlags.TransparentMouse).LastOrDefault (); + View? found = Application.Current.GetViewsUnderLocation (new (testX, testY), ViewportSettingsFlags.TransparentMouse).LastOrDefault (); Assert.Equal (expectedSubViewFound, found == subview); - Application.Top.Dispose (); + Application.Current.Dispose (); Application.ResetState (true); } @@ -184,7 +184,7 @@ public class GetViewsUnderLocationTests [InlineData (5, 6, false)] public void Returns_Null_If_SubView_NotVisible (int testX, int testY, bool expectedSubViewFound) { - Application.Top = new () + Application.Current = new () { Width = 10, Height = 10 }; @@ -195,12 +195,12 @@ public class GetViewsUnderLocationTests Width = 5, Height = 5, Visible = false }; - Application.Top.Add (subview); + Application.Current.Add (subview); - View? found = View.GetViewsUnderLocation (new (testX, testY), ViewportSettingsFlags.TransparentMouse).LastOrDefault (); + View? found = Application.Current.GetViewsUnderLocation (new (testX, testY), ViewportSettingsFlags.TransparentMouse).LastOrDefault (); Assert.Equal (expectedSubViewFound, found == subview); - Application.Top.Dispose (); + Application.Current.Dispose (); Application.ResetState (true); } @@ -214,7 +214,7 @@ public class GetViewsUnderLocationTests [InlineData (5, 6, false)] public void Returns_Null_If_Not_Visible_And_SubView_Visible (int testX, int testY, bool expectedSubViewFound) { - Application.Top = new () + Application.Current = new () { Width = 10, Height = 10, Visible = false @@ -225,14 +225,14 @@ public class GetViewsUnderLocationTests X = 1, Y = 2, Width = 5, Height = 5 }; - Application.Top.Add (subview); + Application.Current.Add (subview); subview.Visible = true; Assert.True (subview.Visible); - Assert.False (Application.Top.Visible); - View? found = View.GetViewsUnderLocation (new (testX, testY), ViewportSettingsFlags.TransparentMouse).LastOrDefault (); + Assert.False (Application.Current.Visible); + View? found = Application.Current.GetViewsUnderLocation (new (testX, testY), ViewportSettingsFlags.TransparentMouse).LastOrDefault (); Assert.Equal (expectedSubViewFound, found == subview); - Application.Top.Dispose (); + Application.Current.Dispose (); Application.ResetState (true); } @@ -249,23 +249,23 @@ public class GetViewsUnderLocationTests [InlineData (6, 7, true)] public void Returns_Correct_If_Start_Has_Adornments (int testX, int testY, bool expectedSubViewFound) { - Application.Top = new () + Application.Current = new () { Width = 10, Height = 10 }; - Application.Top.Margin!.Thickness = new (1); + Application.Current.Margin!.Thickness = new (1); var subview = new View { X = 1, Y = 2, Width = 5, Height = 5 }; - Application.Top.Add (subview); + Application.Current.Add (subview); - View? found = View.GetViewsUnderLocation (new (testX, testY), ViewportSettingsFlags.TransparentMouse).LastOrDefault (); + View? found = Application.Current.GetViewsUnderLocation (new (testX, testY), ViewportSettingsFlags.TransparentMouse).LastOrDefault (); Assert.Equal (expectedSubViewFound, found == subview); - Application.Top.Dispose (); + Application.Current.Dispose (); Application.ResetState (true); } @@ -280,24 +280,24 @@ public class GetViewsUnderLocationTests [InlineData (-1, 0, 0, false)] public void Returns_Correct_If_Start_Has_Offset_Viewport (int offset, int testX, int testY, bool expectedSubViewFound) { - Application.Top = new () + Application.Current = new () { Width = 10, Height = 10, ViewportSettings = ViewportSettingsFlags.AllowNegativeLocation }; - Application.Top.Viewport = new (offset, offset, 10, 10); + Application.Current.Viewport = new (offset, offset, 10, 10); var subview = new View { X = 1, Y = 1, Width = 2, Height = 2 }; - Application.Top.Add (subview); + Application.Current.Add (subview); - View? found = View.GetViewsUnderLocation (new (testX, testY), ViewportSettingsFlags.TransparentMouse).LastOrDefault (); + View? found = Application.Current.GetViewsUnderLocation (new (testX, testY), ViewportSettingsFlags.TransparentMouse).LastOrDefault (); Assert.Equal (expectedSubViewFound, found == subview); - Application.Top.Dispose (); + Application.Current.Dispose (); Application.ResetState (true); } @@ -313,25 +313,25 @@ public class GetViewsUnderLocationTests [InlineData (6, 7, false)] public void Returns_Correct_If_Start_Has_Adornment_WithSubView (int testX, int testY, bool expectedSubViewFound) { - Application.Top = new () + Application.Current = new () { Width = 10, Height = 10 }; - Application.Top.Padding!.Thickness = new (1); + Application.Current.Padding!.Thickness = new (1); var subview = new View { X = Pos.AnchorEnd (1), Y = Pos.AnchorEnd (1), Width = 1, Height = 1 }; - Application.Top.Padding.Add (subview); - Application.Top.BeginInit (); - Application.Top.EndInit (); + Application.Current.Padding.Add (subview); + Application.Current.BeginInit (); + Application.Current.EndInit (); - View? found = View.GetViewsUnderLocation (new (testX, testY), ViewportSettingsFlags.TransparentMouse).LastOrDefault (); + View? found = Application.Current.GetViewsUnderLocation (new (testX, testY), ViewportSettingsFlags.TransparentMouse).LastOrDefault (); Assert.Equal (expectedSubViewFound, found == subview); - Application.Top.Dispose (); + Application.Current.Dispose (); Application.ResetState (true); } @@ -347,17 +347,17 @@ public class GetViewsUnderLocationTests { Application.ResetState (true); - Application.Top = new () + Application.Current = new () { Id = "Top", Width = 10, Height = 10 }; - Application.Top.Margin!.Thickness = new (1); - Application.Top.Margin!.Id = "Margin"; - Application.Top.Border!.Thickness = new (1); - Application.Top.Border!.Id = "Border"; - Application.Top.Padding!.Thickness = new (1); - Application.Top.Padding.Id = "Padding"; + Application.Current.Margin!.Thickness = new (1); + Application.Current.Margin!.Id = "Margin"; + Application.Current.Border!.Thickness = new (1); + Application.Current.Border!.Id = "Border"; + Application.Current.Padding!.Thickness = new (1); + Application.Current.Padding.Id = "Padding"; var subview = new View { @@ -365,13 +365,13 @@ public class GetViewsUnderLocationTests X = 1, Y = 1, Width = 1, Height = 1 }; - Application.Top.Add (subview); + Application.Current.Add (subview); - List viewsUnderMouse = View.GetViewsUnderLocation (new (testX, testY), ViewportSettingsFlags.TransparentMouse); + List viewsUnderMouse = Application.Current.GetViewsUnderLocation (new (testX, testY), ViewportSettingsFlags.TransparentMouse); string [] foundIds = viewsUnderMouse.Select (v => v!.Id).ToArray (); Assert.Equal (expectedViewsFound, foundIds); - Application.Top.Dispose (); + Application.Current.Dispose (); Application.ResetState (true); } @@ -388,7 +388,7 @@ public class GetViewsUnderLocationTests [InlineData (2, 3, new [] { "Top", "subview" })] public void Returns_Correct_If_SubView_Has_Adornments (int testX, int testY, string [] expectedViewsFound) { - Application.Top = new () + Application.Current = new () { Id = "Top", Width = 10, Height = 10 @@ -402,13 +402,13 @@ public class GetViewsUnderLocationTests }; subview.Border!.Thickness = new (1); subview.Border!.Id = "border"; - Application.Top.Add (subview); + Application.Current.Add (subview); - List viewsUnderMouse = View.GetViewsUnderLocation (new (testX, testY), ViewportSettingsFlags.TransparentMouse); + List viewsUnderMouse = Application.Current.GetViewsUnderLocation (new (testX, testY), ViewportSettingsFlags.TransparentMouse); string [] foundIds = viewsUnderMouse.Select (v => v!.Id).ToArray (); Assert.Equal (expectedViewsFound, foundIds); - Application.Top.Dispose (); + Application.Current.Dispose (); Application.ResetState (true); } @@ -425,7 +425,7 @@ public class GetViewsUnderLocationTests [InlineData (2, 3, new [] { "Top", "subview" })] public void Returns_Correct_If_SubView_Has_Adornments_With_TransparentMouse (int testX, int testY, string [] expectedViewsFound) { - Application.Top = new () + Application.Current = new () { Id = "Top", Width = 10, Height = 10 @@ -440,13 +440,13 @@ public class GetViewsUnderLocationTests subview.Border!.Thickness = new (1); subview.Border!.ViewportSettings = ViewportSettingsFlags.TransparentMouse; subview.Border!.Id = "border"; - Application.Top.Add (subview); + Application.Current.Add (subview); - List viewsUnderMouse = View.GetViewsUnderLocation (new (testX, testY), ViewportSettingsFlags.TransparentMouse); + List viewsUnderMouse = Application.Current.GetViewsUnderLocation (new (testX, testY), ViewportSettingsFlags.TransparentMouse); string [] foundIds = viewsUnderMouse.Select (v => v!.Id).ToArray (); Assert.Equal (expectedViewsFound, foundIds); - Application.Top.Dispose (); + Application.Current.Dispose (); Application.ResetState (true); } @@ -463,7 +463,7 @@ public class GetViewsUnderLocationTests [InlineData (5, 5, true)] public void Returns_Correct_If_SubView_Has_Adornment_WithSubView (int testX, int testY, bool expectedSubViewFound) { - Application.Top = new () + Application.Current = new () { Width = 10, Height = 10 }; @@ -486,14 +486,14 @@ public class GetViewsUnderLocationTests Height = 1 }; subview.Padding.Add (paddingSubView); - Application.Top.Add (subview); - Application.Top.BeginInit (); - Application.Top.EndInit (); + Application.Current.Add (subview); + Application.Current.BeginInit (); + Application.Current.EndInit (); - View? found = View.GetViewsUnderLocation (new (testX, testY), ViewportSettingsFlags.TransparentMouse).LastOrDefault (); + View? found = Application.Current.GetViewsUnderLocation (new (testX, testY), ViewportSettingsFlags.TransparentMouse).LastOrDefault (); Assert.Equal (expectedSubViewFound, found == paddingSubView); - Application.Top.Dispose (); + Application.Current.Dispose (); Application.ResetState (true); } @@ -510,7 +510,7 @@ public class GetViewsUnderLocationTests [InlineData (5, 5, true)] public void Returns_Correct_If_SubView_Is_Scrolled_And_Has_Adornment_WithSubView (int testX, int testY, bool expectedSubViewFound) { - Application.Top = new () + Application.Current = new () { Width = 10, Height = 10 }; @@ -537,14 +537,14 @@ public class GetViewsUnderLocationTests Height = 1 }; subview.Padding.Add (paddingSubView); - Application.Top.Add (subview); - Application.Top.BeginInit (); - Application.Top.EndInit (); + Application.Current.Add (subview); + Application.Current.BeginInit (); + Application.Current.EndInit (); - View? found = View.GetViewsUnderLocation (new (testX, testY), ViewportSettingsFlags.TransparentMouse).LastOrDefault (); + View? found = Application.Current.GetViewsUnderLocation (new (testX, testY), ViewportSettingsFlags.TransparentMouse).LastOrDefault (); Assert.Equal (expectedSubViewFound, found == paddingSubView); - Application.Top.Dispose (); + Application.Current.Dispose (); Application.ResetState (true); } @@ -560,7 +560,7 @@ public class GetViewsUnderLocationTests [InlineData (5, 5, 2)] public void Returns_Correct_With_NestedSubViews (int testX, int testY, int expectedSubViewFound) { - Application.Top = new () + Application.Current = new () { Width = 10, Height = 10 }; @@ -583,11 +583,11 @@ public class GetViewsUnderLocationTests } } - Application.Top.Add (subviews [0]); + Application.Current.Add (subviews [0]); - View? found = View.GetViewsUnderLocation (new (testX, testY), ViewportSettingsFlags.TransparentMouse).LastOrDefault (); + View? found = Application.Current.GetViewsUnderLocation (new (testX, testY), ViewportSettingsFlags.TransparentMouse).LastOrDefault (); Assert.Equal (expectedSubViewFound, subviews.IndexOf (found!)); - Application.Top.Dispose (); + Application.Current.Dispose (); Application.ResetState (true); } @@ -604,7 +604,7 @@ public class GetViewsUnderLocationTests public void Tiled_SubViews (int mouseX, int mouseY, string [] viewIdStrings) { // Arrange - Application.Top = new () + Application.Current = new () { Frame = new (0, 0, 10, 10), Id = "top" @@ -630,15 +630,15 @@ public class GetViewsUnderLocationTests Arrangement = ViewArrangement.Overlapped }; // at 2,2 to 4,3 (screen) view.Add (subView); - Application.Top.Add (view); + Application.Current.Add (view); - List found = View.GetViewsUnderLocation (new (mouseX, mouseY), ViewportSettingsFlags.TransparentMouse); + List found = Application.Current.GetViewsUnderLocation (new (mouseX, mouseY), ViewportSettingsFlags.TransparentMouse); string [] foundIds = found.Select (v => v!.Id).ToArray (); Assert.Equal (viewIdStrings, foundIds); - Application.Top.Dispose (); + Application.Current.Dispose (); Application.ResetState (true); } @@ -656,7 +656,7 @@ public class GetViewsUnderLocationTests public void Popover (int mouseX, int mouseY, string [] viewIdStrings) { // Arrange - Application.Top = new () + Application.Current = new () { Frame = new (0, 0, 10, 10), Id = "top" @@ -683,15 +683,15 @@ public class GetViewsUnderLocationTests }; // at 2,2 to 4,3 (screen) view.Add (popOver); - Application.Top.Add (view); + Application.Current.Add (view); - List found = View.GetViewsUnderLocation (new (mouseX, mouseY), ViewportSettingsFlags.TransparentMouse); + List found = Application.Current.GetViewsUnderLocation (new (mouseX, mouseY), ViewportSettingsFlags.TransparentMouse); string [] foundIds = found.Select (v => v!.Id).ToArray (); Assert.Equal (viewIdStrings, foundIds); - Application.Top.Dispose (); + Application.Current.Dispose (); Application.ResetState (true); } @@ -714,18 +714,18 @@ public class GetViewsUnderLocationTests secondaryToplevel.Margin!.Thickness = new (1); secondaryToplevel.Layout (); - Application.TopLevels.Clear (); - Application.TopLevels.Push (topToplevel); - Application.TopLevels.Push (secondaryToplevel); - Application.Top = secondaryToplevel; + Application.SessionStack.Clear (); + Application.SessionStack.Push (topToplevel); + Application.SessionStack.Push (secondaryToplevel); + Application.Current = secondaryToplevel; - List found = View.GetViewsUnderLocation (new (2, 2), ViewportSettingsFlags.TransparentMouse); + List found = Application.Current.GetViewsUnderLocation (new (2, 2), ViewportSettingsFlags.TransparentMouse); Assert.Contains (found, v => v?.Id == topToplevel.Id); Assert.Contains (found, v => v == topToplevel); topToplevel.Dispose (); secondaryToplevel.Dispose (); - Application.TopLevels.Clear (); + Application.SessionStack.Clear (); Application.ResetState (true); } @@ -748,18 +748,18 @@ public class GetViewsUnderLocationTests secondaryToplevel.Margin!.Thickness = new (1); secondaryToplevel.Layout (); - Application.TopLevels.Clear (); - Application.TopLevels.Push (topToplevel); - Application.TopLevels.Push (secondaryToplevel); - Application.Top = secondaryToplevel; + Application.SessionStack.Clear (); + Application.SessionStack.Push (topToplevel); + Application.SessionStack.Push (secondaryToplevel); + Application.Current = secondaryToplevel; - List found = View.GetViewsUnderLocation (new (7, 7), ViewportSettingsFlags.TransparentMouse); + List found = Application.Current.GetViewsUnderLocation (new (7, 7), ViewportSettingsFlags.TransparentMouse); Assert.Contains (found, v => v?.Id == secondaryToplevel.Id); Assert.DoesNotContain (found, v => v?.Id == topToplevel.Id); topToplevel.Dispose (); secondaryToplevel.Dispose (); - Application.TopLevels.Clear (); + Application.SessionStack.Clear (); Application.ResetState (true); } @@ -781,27 +781,27 @@ public class GetViewsUnderLocationTests }; secondaryToplevel.Margin!.Thickness = new (1); - Application.TopLevels.Clear (); - Application.TopLevels.Push (topToplevel); - Application.TopLevels.Push (secondaryToplevel); - Application.Top = secondaryToplevel; + Application.SessionStack.Clear (); + Application.SessionStack.Push (topToplevel); + Application.SessionStack.Push (secondaryToplevel); + Application.Current = secondaryToplevel; secondaryToplevel.Margin!.ViewportSettings = ViewportSettingsFlags.None; - List found = View.GetViewsUnderLocation (new (5, 5), ViewportSettingsFlags.TransparentMouse); + List found = Application.Current.GetViewsUnderLocation (new (5, 5), ViewportSettingsFlags.TransparentMouse); Assert.Contains (found, v => v == secondaryToplevel); Assert.Contains (found, v => v == secondaryToplevel.Margin); Assert.DoesNotContain (found, v => v?.Id == topToplevel.Id); secondaryToplevel.Margin!.ViewportSettings = ViewportSettingsFlags.TransparentMouse; - found = View.GetViewsUnderLocation (new (5, 5), ViewportSettingsFlags.TransparentMouse); + found = Application.Current.GetViewsUnderLocation (new (5, 5), ViewportSettingsFlags.TransparentMouse); Assert.DoesNotContain (found, v => v == secondaryToplevel); Assert.DoesNotContain (found, v => v == secondaryToplevel.Margin); Assert.Contains (found, v => v?.Id == topToplevel.Id); topToplevel.Dispose (); secondaryToplevel.Dispose (); - Application.TopLevels.Clear (); + Application.SessionStack.Clear (); Application.ResetState (true); } @@ -824,17 +824,17 @@ public class GetViewsUnderLocationTests secondaryToplevel.Margin!.Thickness = new (1); secondaryToplevel.Layout (); - Application.TopLevels.Clear (); - Application.TopLevels.Push (topToplevel); - Application.TopLevels.Push (secondaryToplevel); - Application.Top = secondaryToplevel; + Application.SessionStack.Clear (); + Application.SessionStack.Push (topToplevel); + Application.SessionStack.Push (secondaryToplevel); + Application.Current = secondaryToplevel; - List found = View.GetViewsUnderLocation (new (20, 20), ViewportSettingsFlags.TransparentMouse); + List found = Application.Current.GetViewsUnderLocation (new (20, 20), ViewportSettingsFlags.TransparentMouse); Assert.Empty (found); topToplevel.Dispose (); secondaryToplevel.Dispose (); - Application.TopLevels.Clear (); + Application.SessionStack.Clear (); Application.ResetState (true); } } diff --git a/Tests/UnitTests/View/Layout/Pos.CombineTests.cs b/Tests/UnitTests/View/Layout/Pos.CombineTests.cs index f6ca4edcf..cbbf50da6 100644 --- a/Tests/UnitTests/View/Layout/Pos.CombineTests.cs +++ b/Tests/UnitTests/View/Layout/Pos.CombineTests.cs @@ -40,7 +40,7 @@ public class PosCombineTests (ITestOutputHelper output) [SetupFakeApplication] public void PosCombine_DimCombine_View_With_SubViews () { - Application.Top = new Toplevel () { Width = 80, Height = 25 }; + Application.Current = new Toplevel () { Width = 80, Height = 25 }; var win1 = new Window { Id = "win1", Width = 20, Height = 10 }; var view1 = new View { @@ -59,24 +59,24 @@ public class PosCombineTests (ITestOutputHelper output) view2.Add (view3); win2.Add (view2); win1.Add (view1, win2); - Application.Top.Add (win1); - Application.Top.Layout (); + Application.Current.Add (win1); + Application.Current.Layout (); - Assert.Equal (new Rectangle (0, 0, 80, 25), Application.Top.Frame); + Assert.Equal (new Rectangle (0, 0, 80, 25), Application.Current.Frame); Assert.Equal (new Rectangle (0, 0, 5, 1), view1.Frame); Assert.Equal (new Rectangle (0, 0, 20, 10), win1.Frame); Assert.Equal (new Rectangle (0, 2, 10, 3), win2.Frame); Assert.Equal (new Rectangle (0, 0, 8, 1), view2.Frame); Assert.Equal (new Rectangle (0, 0, 7, 1), view3.Frame); - var foundView = View.GetViewsUnderLocation (new Point(9, 4), ViewportSettingsFlags.None).LastOrDefault (); + var foundView = Application.Current.GetViewsUnderLocation (new Point(9, 4), ViewportSettingsFlags.None).LastOrDefault (); Assert.Equal (foundView, view2); - Application.Top.Dispose (); + Application.Current.Dispose (); } [Fact] public void PosCombine_Refs_SuperView_Throws () { - Application.Init (null, "fake"); + Application.Init ("fake"); var top = new Toplevel (); var w = new Window { X = Pos.Left (top) + 2, Y = Pos.Top (top) + 2 }; @@ -89,13 +89,13 @@ public class PosCombineTests (ITestOutputHelper output) top.Add (w); Application.Begin (top); - f.X = Pos.X (Application.Top) + Pos.X (v2) - Pos.X (v1); - f.Y = Pos.Y (Application.Top) + Pos.Y (v2) - Pos.Y (v1); + f.X = Pos.X (Application.Current) + Pos.X (v2) - Pos.X (v1); + f.Y = Pos.Y (Application.Current) + Pos.Y (v2) - Pos.Y (v1); - Application.Top.SubViewsLaidOut += (s, e) => + Application.Current.SubViewsLaidOut += (s, e) => { - Assert.Equal (0, Application.Top.Frame.X); - Assert.Equal (0, Application.Top.Frame.Y); + Assert.Equal (0, Application.Current.Frame.X); + Assert.Equal (0, Application.Current.Frame.Y); Assert.Equal (2, w.Frame.X); Assert.Equal (2, w.Frame.Y); Assert.Equal (2, f.Frame.X); @@ -109,7 +109,7 @@ public class PosCombineTests (ITestOutputHelper output) Application.StopAfterFirstIteration = true; Assert.Throws (() => Application.Run ()); - Application.Top.Dispose (); + Application.Current.Dispose (); top.Dispose (); Application.Shutdown (); } diff --git a/Tests/UnitTests/View/Layout/Pos.Tests.cs b/Tests/UnitTests/View/Layout/Pos.Tests.cs index 4d419b29a..256c1c6e1 100644 --- a/Tests/UnitTests/View/Layout/Pos.Tests.cs +++ b/Tests/UnitTests/View/Layout/Pos.Tests.cs @@ -11,7 +11,7 @@ public class PosTests () public void Pos_Validation_Do_Not_Throws_If_NewValue_Is_PosAbsolute_And_OldValue_Is_Another_Type_After_Sets_To_LayoutStyle_Absolute () { - Application.Init (null, "fake"); + Application.Init ("fake"); Toplevel t = new (); @@ -43,7 +43,7 @@ public class PosTests () [TestRespondersDisposed] public void PosCombine_WHY_Throws () { - Application.Init (null, "fake"); + Application.Init ("fake"); Toplevel t = new Toplevel (); @@ -133,7 +133,7 @@ public class PosTests () [TestRespondersDisposed] public void Pos_Subtract_Operator () { - Application.Init (null, "fake"); + Application.Init ("fake"); Toplevel top = new (); @@ -207,7 +207,7 @@ public class PosTests () [Fact] public void Pos_Validation_Do_Not_Throws_If_NewValue_Is_PosAbsolute_And_OldValue_Is_Null () { - Application.Init (null, "fake"); + Application.Init ("fake"); Toplevel t = new (); @@ -233,7 +233,7 @@ public class PosTests () [Fact] public void Validation_Does_Not_Throw_If_NewValue_Is_PosAbsolute_And_OldValue_Is_Null () { - Application.Init (null, "fake"); + Application.Init ("fake"); Toplevel t = new Toplevel (); diff --git a/Tests/UnitTests/View/Layout/SetLayoutTests.cs b/Tests/UnitTests/View/Layout/SetLayoutTests.cs index ac3654e29..7ccf52dca 100644 --- a/Tests/UnitTests/View/Layout/SetLayoutTests.cs +++ b/Tests/UnitTests/View/Layout/SetLayoutTests.cs @@ -12,7 +12,7 @@ public class SetLayoutTests (ITestOutputHelper output) [AutoInitShutdown] public void Screen_Size_Change_Causes_Layout () { - Application.Top = new (); + Application.Current = new (); var view = new View { @@ -22,21 +22,21 @@ public class SetLayoutTests (ITestOutputHelper output) Height = 1, Text = "0123456789" }; - Application.Top.Add (view); + Application.Current.Add (view); - var rs = Application.Begin (Application.Top); + var rs = Application.Begin (Application.Current); Application.Driver!.SetScreenSize (80, 25); Assert.Equal (new (0, 0, 80, 25), new Rectangle (0, 0, Application.Screen.Width, Application.Screen.Height)); - Assert.Equal (new (0, 0, Application.Screen.Width, Application.Screen.Height), Application.Top.Frame); - Assert.Equal (new (0, 0, 80, 25), Application.Top.Frame); + Assert.Equal (new (0, 0, Application.Screen.Width, Application.Screen.Height), Application.Current.Frame); + Assert.Equal (new (0, 0, 80, 25), Application.Current.Frame); Application.Driver!.SetScreenSize (20, 10); - Assert.Equal (new (0, 0, Application.Screen.Width, Application.Screen.Height), Application.Top.Frame); + Assert.Equal (new (0, 0, Application.Screen.Width, Application.Screen.Height), Application.Current.Frame); - Assert.Equal (new (0, 0, 20, 10), Application.Top.Frame); + Assert.Equal (new (0, 0, 20, 10), Application.Current.Frame); Application.End (rs); - Application.Top.Dispose (); + Application.Current.Dispose (); } } diff --git a/Tests/UnitTests/View/Navigation/CanFocusTests.cs b/Tests/UnitTests/View/Navigation/CanFocusTests.cs index 679239fdc..8a82c605d 100644 --- a/Tests/UnitTests/View/Navigation/CanFocusTests.cs +++ b/Tests/UnitTests/View/Navigation/CanFocusTests.cs @@ -88,20 +88,21 @@ public class CanFocusTests [Fact] public void CanFocus_Set_True_Get_AdvanceFocus_Works () { + IApplication app = Application.Create (); + app.Current = new () { App = app }; + Label label = new () { Text = "label" }; View view = new () { Text = "view", CanFocus = true }; - Application.Navigation = new (); - Application.Top = new (); - Application.Top.Add (label, view); + app.Current.Add (label, view); - Application.Top.SetFocus (); - Assert.Equal (view, Application.Navigation.GetFocused ()); + app.Current.SetFocus (); + Assert.Equal (view, app.Navigation!.GetFocused ()); Assert.False (label.CanFocus); Assert.False (label.HasFocus); Assert.True (view.CanFocus); Assert.True (view.HasFocus); - Assert.False (Application.Navigation.AdvanceFocus (NavigationDirection.Forward, null)); + Assert.False (app.Navigation.AdvanceFocus (NavigationDirection.Forward, null)); Assert.False (label.HasFocus); Assert.True (view.HasFocus); @@ -111,7 +112,7 @@ public class CanFocusTests Assert.True (view.HasFocus); // label can now be focused, so AdvanceFocus should move to it. - Assert.True (Application.Navigation.AdvanceFocus (NavigationDirection.Forward, null)); + Assert.True (app.Navigation.AdvanceFocus (NavigationDirection.Forward, null)); Assert.True (label.HasFocus); Assert.False (view.HasFocus); @@ -120,11 +121,11 @@ public class CanFocusTests Assert.False (label.HasFocus); Assert.True (view.HasFocus); - Assert.True (Application.RaiseKeyDownEvent (Key.Tab)); + Assert.True (app.Keyboard.RaiseKeyDownEvent (Key.Tab)); Assert.True (label.HasFocus); Assert.False (view.HasFocus); - Application.Top.Dispose (); - Application.ResetState (); + app.Current.Dispose (); + app.ResetState (); } } diff --git a/Tests/UnitTests/View/Navigation/NavigationTests.cs b/Tests/UnitTests/View/Navigation/NavigationTests.cs index 8198b14eb..466d6a1ca 100644 --- a/Tests/UnitTests/View/Navigation/NavigationTests.cs +++ b/Tests/UnitTests/View/Navigation/NavigationTests.cs @@ -27,8 +27,7 @@ public class NavigationTests (ITestOutputHelper output) : TestsAllViews } Toplevel top = new (); - Application.Top = top; - Application.Navigation = new (); + Application.Current = top; View otherView = new () { @@ -118,8 +117,7 @@ public class NavigationTests (ITestOutputHelper output) : TestsAllViews } Toplevel top = new (); - Application.Top = top; - Application.Navigation = new (); + Application.Current = top; View otherView = new () { @@ -150,8 +148,8 @@ public class NavigationTests (ITestOutputHelper output) : TestsAllViews // Ensure the view is Visible view.Visible = true; - Application.Top.SetFocus (); - Assert.True (Application.Top!.HasFocus); + Application.Current.SetFocus (); + Assert.True (Application.Current!.HasFocus); Assert.True (top.HasFocus); // Start with the focus on our test view @@ -282,8 +280,7 @@ public class NavigationTests (ITestOutputHelper output) : TestsAllViews Toplevel top = new (); - Application.Top = top; - Application.Navigation = new (); + Application.Current = top; View otherView = new () { diff --git a/Tests/UnitTests/View/TextTests.cs b/Tests/UnitTests/View/TextTests.cs index 3e552204c..625d0ce49 100644 --- a/Tests/UnitTests/View/TextTests.cs +++ b/Tests/UnitTests/View/TextTests.cs @@ -13,6 +13,7 @@ public class TextTests (ITestOutputHelper output) public void Setting_With_Height_Horizontal () { var top = new View { Width = 25, Height = 25 }; + top.App = ApplicationImpl.Instance; var label = new Label { Text = "Hello", /* Width = 10, Height = 2, */ ValidatePosDim = true }; var viewX = new View { Text = "X", X = Pos.Right (label), Width = 1, Height = 1 }; @@ -39,7 +40,7 @@ Y Assert.Equal (new (0, 0, 10, 2), label.Frame); top.LayoutSubViews (); - View.SetClipToScreen (); + top.SetClipToScreen (); top.Draw (); expected = @" @@ -394,7 +395,7 @@ Y Assert.Equal (new (1, 5), view.TextFormatter.ConstrainToSize); Assert.Equal (new () { "Views" }, view.TextFormatter.GetLines ()); Assert.Equal (new (0, 0, 4, 10), win.Frame); - Assert.Equal (new (0, 0, 4, 10), Application.Top.Frame); + Assert.Equal (new (0, 0, 4, 10), Application.Current.Frame); var expected = @" ┌──┐ @@ -449,6 +450,7 @@ Y var view = new View { + App = ApplicationImpl.Instance, TextDirection = TextDirection.TopBottom_LeftRight, Text = text, Width = Dim.Auto (), @@ -1000,6 +1002,7 @@ w "; { Application.Driver!.SetScreenSize (32, 32); var top = new View { Width = 32, Height = 32 }; + top.App = ApplicationImpl.Instance; var text = $"First line{Environment.NewLine}Second line"; var horizontalView = new View { Width = 20, Height = 1, Text = text }; @@ -1075,7 +1078,7 @@ w "; verticalView.Width = 2; verticalView.TextFormatter.ConstrainToSize = new (2, 20); Assert.True (verticalView.TextFormatter.NeedsFormat); - View.SetClipToScreen (); + top.SetClipToScreen (); top.Draw (); Assert.Equal (new (0, 3, 2, 20), verticalView.Frame); @@ -1124,7 +1127,7 @@ w "; View view; var text = "test"; - view = new Label { Text = text }; + view = new Label { App = ApplicationImpl.Instance, Text = text }; view.BeginInit (); view.EndInit (); view.Draw (); diff --git a/Tests/UnitTests/View/ViewCommandTests.cs b/Tests/UnitTests/View/ViewCommandTests.cs index 9564aefdd..cc48653eb 100644 --- a/Tests/UnitTests/View/ViewCommandTests.cs +++ b/Tests/UnitTests/View/ViewCommandTests.cs @@ -46,9 +46,9 @@ public class ViewCommandTests w.LayoutSubViews (); - Application.Top = w; - Application.TopLevels.Push (w); - Assert.Same (Application.Top, w); + Application.Current = w; + Application.SessionStack.Push (w); + Assert.Same (Application.Current, w); // Click button 2 Rectangle btn2Frame = btnB.FrameToScreen (); @@ -121,9 +121,9 @@ public class ViewCommandTests w.Add (btn); - Application.Top = w; - Application.TopLevels.Push (w); - Assert.Same (Application.Top, w); + Application.Current = w; + Application.SessionStack.Push (w); + Assert.Same (Application.Current, w); w.LayoutSubViews (); diff --git a/Tests/UnitTests/View/ViewTests.cs b/Tests/UnitTests/View/ViewTests.cs index 42d89f39a..f42715e6e 100644 --- a/Tests/UnitTests/View/ViewTests.cs +++ b/Tests/UnitTests/View/ViewTests.cs @@ -332,6 +332,7 @@ public class ViewTests public void Test_Nested_Views_With_Height_Equal_To_One () { var v = new View { Width = 11, Height = 3 }; + v.App = ApplicationImpl.Instance; var top = new View { Width = Dim.Fill (), Height = 1 }; var bottom = new View { Width = Dim.Fill (), Height = 1, Y = 2 }; diff --git a/Tests/UnitTests/View/Viewport/ViewportSettings.TransparentMouseTests.cs b/Tests/UnitTests/View/Viewport/ViewportSettings.TransparentMouseTests.cs index 089753bf7..2e66c4c9b 100644 --- a/Tests/UnitTests/View/Viewport/ViewportSettings.TransparentMouseTests.cs +++ b/Tests/UnitTests/View/Viewport/ViewportSettings.TransparentMouseTests.cs @@ -23,7 +23,7 @@ public class TransparentMouseTests { Id = "top", }; - Application.Top = top; + Application.Current = top; var underlying = new MouseTrackingView { Id = "underlying", X = 0, Y = 0, Width = 10, Height = 10 }; var overlay = new MouseTrackingView { Id = "overlay", X = 0, Y = 0, Width = 10, Height = 10, ViewportSettings = ViewportSettingsFlags.TransparentMouse }; @@ -56,7 +56,7 @@ public class TransparentMouseTests { // Arrange var top = new Toplevel (); - Application.Top = top; + Application.Current = top; var underlying = new MouseTrackingView { X = 0, Y = 0, Width = 10, Height = 10 }; var overlay = new MouseTrackingView { X = 0, Y = 0, Width = 10, Height = 10, ViewportSettings = ViewportSettingsFlags.None }; @@ -90,7 +90,7 @@ public class TransparentMouseTests { // Arrange var top = new Toplevel (); - Application.Top = top; + Application.Current = top; var underlying = new MouseTrackingView { X = 0, Y = 0, Width = 10, Height = 10, ViewportSettings = ViewportSettingsFlags.TransparentMouse }; var overlay = new MouseTrackingView { X = 0, Y = 0, Width = 10, Height = 10, ViewportSettings = ViewportSettingsFlags.TransparentMouse }; diff --git a/Tests/UnitTests/Views/AppendAutocompleteTests.cs b/Tests/UnitTests/Views/AppendAutocompleteTests.cs index a3c910710..3fa75b7b8 100644 --- a/Tests/UnitTests/Views/AppendAutocompleteTests.cs +++ b/Tests/UnitTests/Views/AppendAutocompleteTests.cs @@ -13,9 +13,9 @@ public class AppendAutocompleteTests (ITestOutputHelper output) // f is typed and suggestion is "fish" Application.RaiseKeyDownEvent ('f'); - View.SetClipToScreen (); + tf.SetClipToScreen (); tf.Draw (); - View.SetClipToScreen (); + tf.SetClipToScreen (); tf.PositionCursor (); DriverAssert.AssertDriverContentsAre ("fish", output); Assert.Equal ("f", tf.Text); @@ -25,17 +25,17 @@ public class AppendAutocompleteTests (ITestOutputHelper output) // Suggestion should disappear tf.Draw (); - View.SetClipToScreen (); + tf.SetClipToScreen (); DriverAssert.AssertDriverContentsAre ("f", output); Assert.Equal ("f", tf.Text); // Still has focus though - Assert.Same (tf, Application.Top.Focused); + Assert.Same (tf, Application.Current.Focused); // But can tab away Application.RaiseKeyDownEvent ('\t'); - Assert.NotSame (tf, Application.Top.Focused); - Application.Top.Dispose (); + Assert.NotSame (tf, Application.Current.Focused); + Application.Current.Dispose (); } [Fact] @@ -46,9 +46,9 @@ public class AppendAutocompleteTests (ITestOutputHelper output) // f is typed and suggestion is "fish" Application.RaiseKeyDownEvent ('f'); - View.SetClipToScreen (); + tf.SetClipToScreen (); tf.Draw (); - View.SetClipToScreen (); + tf.SetClipToScreen (); tf.PositionCursor (); DriverAssert.AssertDriverContentsAre ("fish", output); Assert.Equal ("f", tf.Text); @@ -63,13 +63,13 @@ public class AppendAutocompleteTests (ITestOutputHelper output) // Should reappear when you press next letter Application.RaiseKeyDownEvent (Key.I); - View.SetClipToScreen (); + tf.SetClipToScreen (); tf.Draw (); - View.SetClipToScreen (); + tf.SetClipToScreen (); tf.PositionCursor (); DriverAssert.AssertDriverContentsAre ("fish", output); Assert.Equal ("fi", tf.Text); - Application.Top.Dispose (); + Application.Current.Dispose (); } [Theory] @@ -82,9 +82,9 @@ public class AppendAutocompleteTests (ITestOutputHelper output) // f is typed and suggestion is "fish" Application.RaiseKeyDownEvent ('f'); - View.SetClipToScreen (); + tf.SetClipToScreen (); tf.Draw (); - View.SetClipToScreen (); + tf.SetClipToScreen (); tf.PositionCursor (); DriverAssert.AssertDriverContentsAre ("fish", output); Assert.Equal ("f", tf.Text); @@ -92,22 +92,22 @@ public class AppendAutocompleteTests (ITestOutputHelper output) // When cycling autocomplete Application.RaiseKeyDownEvent (cycleKey); - View.SetClipToScreen (); + tf.SetClipToScreen (); tf.Draw (); - View.SetClipToScreen (); + tf.SetClipToScreen (); tf.PositionCursor (); DriverAssert.AssertDriverContentsAre ("friend", output); Assert.Equal ("f", tf.Text); // Should be able to cycle in circles endlessly Application.RaiseKeyDownEvent (cycleKey); - View.SetClipToScreen (); + tf.SetClipToScreen (); tf.Draw (); - View.SetClipToScreen (); + tf.SetClipToScreen (); tf.PositionCursor (); DriverAssert.AssertDriverContentsAre ("fish", output); Assert.Equal ("f", tf.Text); - Application.Top.Dispose (); + Application.Current.Dispose (); } [Fact] @@ -118,9 +118,9 @@ public class AppendAutocompleteTests (ITestOutputHelper output) // f is typed and suggestion is "fish" Application.RaiseKeyDownEvent ('f'); - View.SetClipToScreen (); + tf.SetClipToScreen (); tf.Draw (); - View.SetClipToScreen (); + tf.SetClipToScreen (); tf.PositionCursor (); DriverAssert.AssertDriverContentsAre ("fish", output); Assert.Equal ("f", tf.Text); @@ -129,11 +129,11 @@ public class AppendAutocompleteTests (ITestOutputHelper output) Application.RaiseKeyDownEvent (' '); Application.RaiseKeyDownEvent (Key.CursorLeft); - View.SetClipToScreen (); + tf.SetClipToScreen (); tf.Draw (); DriverAssert.AssertDriverContentsAre ("f", output); Assert.Equal ("f ", tf.Text); - Application.Top.Dispose (); + Application.Current.Dispose (); } [Fact] @@ -144,20 +144,20 @@ public class AppendAutocompleteTests (ITestOutputHelper output) // f is typed and suggestion is "fish" Application.RaiseKeyDownEvent ('f'); - View.SetClipToScreen (); + tf.SetClipToScreen (); tf.Draw (); - View.SetClipToScreen (); + tf.SetClipToScreen (); tf.PositionCursor (); DriverAssert.AssertDriverContentsAre ("fish", output); Assert.Equal ("f", tf.Text); // x is typed and suggestion should disappear Application.RaiseKeyDownEvent (Key.X); - View.SetClipToScreen (); + tf.SetClipToScreen (); tf.Draw (); DriverAssert.AssertDriverContentsAre ("fx", output); Assert.Equal ("fx", tf.Text); - Application.Top.Dispose (); + Application.Current.Dispose (); } [Fact] @@ -170,9 +170,9 @@ public class AppendAutocompleteTests (ITestOutputHelper output) var generator = (SingleWordSuggestionGenerator)tf.Autocomplete.SuggestionGenerator; generator.AllSuggestions = new() { "FISH" }; - View.SetClipToScreen (); + tf.SetClipToScreen (); tf.Draw (); - View.SetClipToScreen (); + tf.SetClipToScreen (); tf.PositionCursor (); DriverAssert.AssertDriverContentsAre ("", output); tf.NewKeyDownEvent (Key.M); @@ -182,20 +182,20 @@ public class AppendAutocompleteTests (ITestOutputHelper output) Assert.Equal ("my f", tf.Text); // Even though there is no match on case we should still get the suggestion - View.SetClipToScreen (); + tf.SetClipToScreen (); tf.Draw (); - View.SetClipToScreen (); + tf.SetClipToScreen (); tf.PositionCursor (); DriverAssert.AssertDriverContentsAre ("my fISH", output); Assert.Equal ("my f", tf.Text); // When tab completing the case of the whole suggestion should be applied Application.RaiseKeyDownEvent ('\t'); - View.SetClipToScreen (); + tf.SetClipToScreen (); tf.Draw (); DriverAssert.AssertDriverContentsAre ("my FISH", output); Assert.Equal ("my FISH", tf.Text); - Application.Top.Dispose (); + Application.Current.Dispose (); } [Fact] @@ -208,35 +208,35 @@ public class AppendAutocompleteTests (ITestOutputHelper output) var generator = (SingleWordSuggestionGenerator)tf.Autocomplete.SuggestionGenerator; generator.AllSuggestions = new() { "fish" }; - View.SetClipToScreen (); + tf.SetClipToScreen (); tf.Draw (); - View.SetClipToScreen (); + tf.SetClipToScreen (); tf.PositionCursor (); DriverAssert.AssertDriverContentsAre ("", output); tf.NewKeyDownEvent (new ('f')); - View.SetClipToScreen (); + tf.SetClipToScreen (); tf.Draw (); - View.SetClipToScreen (); + tf.SetClipToScreen (); tf.PositionCursor (); DriverAssert.AssertDriverContentsAre ("fish", output); Assert.Equal ("f", tf.Text); Application.RaiseKeyDownEvent ('\t'); - View.SetClipToScreen (); + tf.SetClipToScreen (); tf.Draw (); DriverAssert.AssertDriverContentsAre ("fish", output); Assert.Equal ("fish", tf.Text); // Tab should autcomplete but not move focus - Assert.Same (tf, Application.Top.Focused); + Assert.Same (tf, Application.Current.Focused); // Second tab should move focus (nothing to autocomplete) Application.RaiseKeyDownEvent ('\t'); - Assert.NotSame (tf, Application.Top.Focused); - Application.Top.Dispose (); + Assert.NotSame (tf, Application.Current.Focused); + Application.Current.Dispose (); } [Theory] @@ -250,13 +250,13 @@ public class AppendAutocompleteTests (ITestOutputHelper output) // f is typed we should only see 'f' up to size of View (10) Application.RaiseKeyDownEvent ('f'); - View.SetClipToScreen (); + tf.SetClipToScreen (); tf.Draw (); - View.SetClipToScreen (); + tf.SetClipToScreen (); tf.PositionCursor (); DriverAssert.AssertDriverContentsAre (expectRender, output); Assert.Equal ("f", tf.Text); - Application.Top.Dispose (); + Application.Current.Dispose (); } private TextField GetTextFieldsInView () diff --git a/Tests/UnitTests/Views/ButtonTests.cs b/Tests/UnitTests/Views/ButtonTests.cs index d05a3fc53..83d8cc032 100644 --- a/Tests/UnitTests/Views/ButtonTests.cs +++ b/Tests/UnitTests/Views/ButtonTests.cs @@ -13,7 +13,10 @@ public class ButtonTests (ITestOutputHelper output) // Override CM Button.DefaultShadow = ShadowStyle.None; - var btn = new Button (); + var btn = new Button () + { + App = ApplicationImpl.Instance + }; Assert.Equal (string.Empty, btn.Text); btn.BeginInit (); btn.EndInit (); @@ -45,7 +48,8 @@ public class ButtonTests (ITestOutputHelper output) DriverAssert.AssertDriverContentsWithFrameAre (expected, output); btn.Dispose (); - btn = new () { Text = "_Test", IsDefault = true }; + btn = new () { App = ApplicationImpl.Instance, + Text = "_Test", IsDefault = true }; btn.Layout (); Assert.Equal (new (10, 1), btn.TextFormatter.ConstrainToSize); @@ -77,7 +81,8 @@ public class ButtonTests (ITestOutputHelper output) btn.Dispose (); - btn = new () { X = 1, Y = 2, Text = "_abc", IsDefault = true }; + btn = new () { App = ApplicationImpl.Instance, + X = 1, Y = 2, Text = "_abc", IsDefault = true }; btn.BeginInit (); btn.EndInit (); Assert.Equal ("_abc", btn.Text); @@ -92,13 +97,13 @@ public class ButtonTests (ITestOutputHelper output) Assert.Equal ('_', btn.HotKeySpecifier.Value); Assert.True (btn.CanFocus); - Application.Driver?.ClearContents (); + ApplicationImpl.Instance.Driver?.ClearContents (); btn.Draw (); expected = @$" {Glyphs.LeftBracket}{Glyphs.LeftDefaultIndicator} abc {Glyphs.RightDefaultIndicator}{Glyphs.RightBracket} "; - DriverAssert.AssertDriverContentsWithFrameAre (expected, output); + DriverAssert.AssertDriverContentsWithFrameAre (expected, output, ApplicationImpl.Instance.Driver); Assert.Equal (new (0, 0, 9, 1), btn.Viewport); Assert.Equal (new (1, 2, 9, 1), btn.Frame); @@ -204,7 +209,7 @@ public class ButtonTests (ITestOutputHelper output) clicked = false; // Toplevel does not handle Enter, so it should get passed on to button - Assert.False (Application.Top.NewKeyDownEvent (Key.Enter)); + Assert.False (Application.Current.NewKeyDownEvent (Key.Enter)); Assert.True (clicked); clicked = false; diff --git a/Tests/UnitTests/Views/CheckBoxTests.cs b/Tests/UnitTests/Views/CheckBoxTests.cs index adfde4db8..39fe5db91 100644 --- a/Tests/UnitTests/Views/CheckBoxTests.cs +++ b/Tests/UnitTests/Views/CheckBoxTests.cs @@ -15,12 +15,11 @@ public class CheckBoxTests (ITestOutputHelper output) [Fact] public void Commands_Select () { - Application.Navigation = new (); - Application.Top = new (); + Application.Current = new (); View otherView = new () { CanFocus = true }; var ckb = new CheckBox (); - Application.Top.Add (ckb, otherView); - Application.Top.SetFocus (); + Application.Current.Add (ckb, otherView); + Application.Current.SetFocus (); Assert.True (ckb.HasFocus); var checkedStateChangingCount = 0; @@ -64,7 +63,7 @@ public class CheckBoxTests (ITestOutputHelper output) Assert.Equal (3, selectCount); Assert.Equal (1, acceptCount); - Application.Top.Dispose (); + Application.Current.Dispose (); Application.ResetState (); } diff --git a/Tests/UnitTests/Views/ColorPickerTests.cs b/Tests/UnitTests/Views/ColorPickerTests.cs index e6beba010..8e454a289 100644 --- a/Tests/UnitTests/Views/ColorPickerTests.cs +++ b/Tests/UnitTests/Views/ColorPickerTests.cs @@ -12,7 +12,7 @@ public class ColorPickerTests var otherView = new View { CanFocus = true }; - Application.Top?.Add (otherView); // thi sets focus to otherView + Application.Current?.Add (otherView); // thi sets focus to otherView Assert.True (otherView.HasFocus); cp.SetFocus (); @@ -772,13 +772,11 @@ public class ColorPickerTests cp.Style.ShowColorName = showName; cp.ApplyStyleChanges (); - Application.Navigation = new (); + Application.Current = new () { Width = 20, Height = 5 }; + Application.Current.Add (cp); - Application.Top = new () { Width = 20, Height = 5 }; - Application.Top.Add (cp); - - Application.Top.LayoutSubViews (); - Application.Top.SetFocus (); + Application.Current.LayoutSubViews (); + Application.Current.SetFocus (); return cp; } diff --git a/Tests/UnitTests/Views/ComboBoxTests.cs b/Tests/UnitTests/Views/ComboBoxTests.cs index e1280424c..260251659 100644 --- a/Tests/UnitTests/Views/ComboBoxTests.cs +++ b/Tests/UnitTests/Views/ComboBoxTests.cs @@ -565,7 +565,7 @@ Three ", Assert.True (cb.IsShow); Assert.Equal (-1, cb.SelectedItem); Assert.Equal ("", cb.Text); - View.SetClipToScreen (); + cb.SetClipToScreen (); cb.Draw (); DriverAssert.AssertDriverAttributesAre ( @@ -584,7 +584,7 @@ Three ", Assert.True (cb.IsShow); Assert.Equal (-1, cb.SelectedItem); Assert.Equal ("", cb.Text); - View.SetClipToScreen (); + cb.SetClipToScreen (); cb.Draw (); DriverAssert.AssertDriverAttributesAre ( @@ -609,7 +609,7 @@ Three ", Assert.True (cb.IsShow); Assert.Equal (2, cb.SelectedItem); Assert.Equal ("Three", cb.Text); - View.SetClipToScreen (); + cb.SetClipToScreen (); cb.Draw (); DriverAssert.AssertDriverAttributesAre ( @@ -628,7 +628,7 @@ Three ", Assert.True (cb.IsShow); Assert.Equal (2, cb.SelectedItem); Assert.Equal ("Three", cb.Text); - View.SetClipToScreen (); + cb.SetClipToScreen (); cb.Draw (); DriverAssert.AssertDriverAttributesAre ( @@ -647,7 +647,7 @@ Three ", Assert.True (cb.IsShow); Assert.Equal (2, cb.SelectedItem); Assert.Equal ("Three", cb.Text); - View.SetClipToScreen (); + cb.SetClipToScreen (); cb.Draw (); DriverAssert.AssertDriverAttributesAre ( @@ -928,7 +928,7 @@ One Assert.Equal (1, cb.SelectedItem); Assert.Equal ("Two", cb.Text); - View.SetClipToScreen (); + cb.SetClipToScreen (); cb.Draw (); DriverAssert.AssertDriverContentsWithFrameAre ( @@ -944,7 +944,7 @@ Two Assert.Equal (2, cb.SelectedItem); Assert.Equal ("Three", cb.Text); - View.SetClipToScreen (); + cb.SetClipToScreen (); cb.Draw (); DriverAssert.AssertDriverContentsWithFrameAre ( @@ -1013,10 +1013,9 @@ Three [Fact] public void Source_Equal_Null_Or_Count_Equal_Zero_Sets_SelectedItem_Equal_To_Minus_One () { - Application.Navigation = new (); var cb = new ComboBox (); var top = new Toplevel (); - Application.Top = top; + Application.Current = top; top.Add (cb); top.FocusDeepest (NavigationDirection.Forward, null); @@ -1055,7 +1054,7 @@ Three Assert.Equal (-1, cb.SelectedItem); Assert.Equal ("", cb.Text); - Application.Top.Dispose (); + Application.Current.Dispose (); Application.ResetState (true); } } diff --git a/Tests/UnitTests/Views/GraphViewTests.cs b/Tests/UnitTests/Views/GraphViewTests.cs index d2184945b..7bcb42a4e 100644 --- a/Tests/UnitTests/Views/GraphViewTests.cs +++ b/Tests/UnitTests/Views/GraphViewTests.cs @@ -44,7 +44,7 @@ internal class FakeVAxis : VerticalAxis #endregion -public class GraphViewTests +public class GraphViewTests : FakeDriverBase { /// /// A cell size of 0 would result in mapping all graph space into the same cell of the console. Since @@ -74,7 +74,10 @@ public class GraphViewTests /// public static GraphView GetGraph () { - var gv = new GraphView (); + var gv = new GraphView () + { + Driver = Application.Driver ?? CreateFakeDriver () + }; gv.BeginInit (); gv.EndInit (); @@ -650,7 +653,7 @@ public class MultiBarSeriesTests fakeXAxis.LabelPoints.Clear (); gv.LayoutSubViews (); gv.SetNeedsDraw (); - View.SetClipToScreen (); + gv.SetClipToScreen (); gv.Draw (); Assert.Equal (3, fakeXAxis.LabelPoints.Count); @@ -677,12 +680,13 @@ public class MultiBarSeriesTests } } -public class BarSeriesTests +public class BarSeriesTests : FakeDriverBase { [Fact] public void TestOneLongOneShortHorizontalBars_WithOffset () { GraphView graph = GetGraph (out FakeBarSeries barSeries, out FakeHAxis axisX, out FakeVAxis axisY); + graph.Driver = CreateFakeDriver (); graph.Draw (); // no bars @@ -1125,7 +1129,7 @@ public class TextAnnotationTests // user scrolls up one unit of graph space gv.ScrollOffset = new PointF (0, 1f); gv.SetNeedsDraw (); - View.SetClipToScreen (); + gv.SetClipToScreen (); gv.Draw (); // we expect the text annotation to go down one line since @@ -1222,7 +1226,7 @@ public class TextAnnotationTests new TextAnnotation { Text = "hey!", ScreenPosition = new Point (3, 1) } ); gv.LayoutSubViews (); - View.SetClipToScreen (); + gv.SetClipToScreen (); gv.Draw (); var expected = @@ -1238,7 +1242,7 @@ public class TextAnnotationTests // user scrolls up one unit of graph space gv.ScrollOffset = new PointF (0, 1f); gv.SetNeedsDraw (); - View.SetClipToScreen (); + gv.SetClipToScreen (); gv.Draw (); // we expect no change in the location of the annotation (only the axis label changes) @@ -1257,7 +1261,7 @@ public class TextAnnotationTests // user scrolls up one unit of graph space gv.ScrollOffset = new PointF (0, 1f); gv.SetNeedsDraw (); - View.SetClipToScreen (); + gv.SetClipToScreen (); gv.Draw (); // we expect no change in the location of the annotation (only the axis label changes) @@ -1385,7 +1389,7 @@ public class PathAnnotationTests "; - DriverAssert.AssertDriverContentsAre (expected, _output); + DriverAssert.AssertDriverContentsAre (expected, _output, gv.Driver); // Shutdown must be called to safely clean up Application if Init has been called Application.Shutdown (); @@ -1410,7 +1414,7 @@ public class PathAnnotationTests "; - DriverAssert.AssertDriverContentsAre (expected, _output); + DriverAssert.AssertDriverContentsAre (expected, _output, gv.Driver); // Shutdown must be called to safely clean up Application if Init has been called Application.Shutdown (); @@ -1528,7 +1532,7 @@ public class PathAnnotationTests // change the text and redraw view.Text = "ff1234"; mount.SetNeedsDraw (); - View.SetClipToScreen (); + top.SetClipToScreen (); mount.Draw (); // should have the new text rendered diff --git a/Tests/UnitTests/Views/HexViewTests.cs b/Tests/UnitTests/Views/HexViewTests.cs index 83d661f6f..63ea65cea 100644 --- a/Tests/UnitTests/Views/HexViewTests.cs +++ b/Tests/UnitTests/Views/HexViewTests.cs @@ -1,6 +1,5 @@ #nullable enable using System.Text; -using JetBrains.Annotations; namespace UnitTests.ViewsTests; @@ -32,10 +31,10 @@ public class HexViewTests public void ReadOnly_Prevents_Edits () { var hv = new HexView (LoadStream (null, out _, true)) { Width = 20, Height = 20 }; - Application.Navigation = new ApplicationNavigation (); - Application.Top = new Toplevel (); - Application.Top.Add (hv); - Application.Top.SetFocus (); + + Application.Current = new (); + Application.Current.Add (hv); + Application.Current.SetFocus (); // Needed because HexView relies on LayoutComplete to calc sizes hv.LayoutSubViews (); @@ -70,15 +69,14 @@ public class HexViewTests Assert.Empty (hv.Edits); Assert.Equal (127, hv.Source.Length); - Application.Top.Dispose (); + Application.Current.Dispose (); Application.ResetState (true); } [Fact] public void ApplyEdits_With_Argument () { - Application.Navigation = new ApplicationNavigation (); - Application.Top = new Toplevel (); + Application.Current = new (); byte [] buffer = Encoding.Default.GetBytes ("Fest"); var original = new MemoryStream (); @@ -89,8 +87,8 @@ public class HexViewTests original.CopyTo (copy); copy.Flush (); var hv = new HexView (copy) { Width = Dim.Fill (), Height = Dim.Fill () }; - Application.Top.Add (hv); - Application.Top.SetFocus (); + Application.Current.Add (hv); + Application.Current.SetFocus (); // Needed because HexView relies on LayoutComplete to calc sizes hv.LayoutSubViews (); @@ -107,7 +105,7 @@ public class HexViewTests Assert.Equal ("Test", Encoding.Default.GetString (readBuffer)); Assert.True (Application.RaiseKeyDownEvent (Key.Tab)); // Move to right side - Assert.True (Application.RaiseKeyDownEvent (Key.CursorLeft)); + Assert.True (Application.RaiseKeyDownEvent (Key.CursorLeft)); Assert.True (Application.RaiseKeyDownEvent (Key.Z.WithShift)); readBuffer [hv.Edits.ToList () [0].Key] = hv.Edits.ToList () [0].Value; Assert.Equal ("Zest", Encoding.Default.GetString (readBuffer)); @@ -121,7 +119,7 @@ public class HexViewTests Assert.Equal ("Zest", Encoding.Default.GetString (readBuffer)); Assert.Equal (Encoding.Default.GetString (buffer), Encoding.Default.GetString (readBuffer)); - Application.Top.Dispose (); + Application.Current.Dispose (); Application.ResetState (true); } @@ -144,13 +142,11 @@ public class HexViewTests [Fact] public void Position_Encoding_Default () { - Application.Navigation = new ApplicationNavigation (); - var hv = new HexView (LoadStream (null, out _)) { Width = 100, Height = 100 }; - Application.Top = new Toplevel (); - Application.Top.Add (hv); + Application.Current = new (); + Application.Current.Add (hv); - Application.Top.LayoutSubViews (); + Application.Current.LayoutSubViews (); Assert.Equal (63, hv.Source!.Length); Assert.Equal (20, hv.BytesPerLine); @@ -175,18 +171,16 @@ public class HexViewTests Assert.Equal (new (3, 3), hv.GetPosition (hv.Address)); Assert.Equal (hv.Source!.Length, hv.Address); - Application.Top.Dispose (); + Application.Current.Dispose (); Application.ResetState (true); } [Fact] public void Position_Encoding_Unicode () { - Application.Navigation = new ApplicationNavigation (); - - var hv = new HexView (LoadStream (null, out _, unicode: true)) { Width = 100, Height = 100 }; - Application.Top = new Toplevel (); - Application.Top.Add (hv); + var hv = new HexView (LoadStream (null, out _, true)) { Width = 100, Height = 100 }; + Application.Current = new (); + Application.Current.Add (hv); hv.LayoutSubViews (); @@ -212,7 +206,7 @@ public class HexViewTests Assert.Equal (new (6, 6), hv.GetPosition (hv.Address)); Assert.Equal (hv.Source!.Length, hv.Address); - Application.Top.Dispose (); + Application.Current.Dispose (); Application.ResetState (true); } @@ -264,10 +258,9 @@ public class HexViewTests [Fact] public void KeyBindings_Test_Movement_LeftSide () { - Application.Navigation = new ApplicationNavigation (); - Application.Top = new Toplevel (); + Application.Current = new (); var hv = new HexView (LoadStream (null, out _)) { Width = 20, Height = 10 }; - Application.Top.Add (hv); + Application.Current.Add (hv); hv.LayoutSubViews (); @@ -313,19 +306,18 @@ public class HexViewTests Assert.True (Application.RaiseKeyDownEvent (Key.CursorUp.WithCtrl)); Assert.Equal (0, hv.Address); - Application.Top.Dispose (); + Application.Current.Dispose (); Application.ResetState (true); } [Fact] public void PositionChanged_Event () { - Application.Navigation = new ApplicationNavigation (); var hv = new HexView (LoadStream (null, out _)) { Width = 20, Height = 10 }; - Application.Top = new Toplevel (); - Application.Top.Add (hv); + Application.Current = new (); + Application.Current.Add (hv); - Application.Top.LayoutSubViews (); + Application.Current.LayoutSubViews (); HexViewEventArgs hexViewEventArgs = null!; hv.PositionChanged += (s, e) => hexViewEventArgs = e; @@ -339,41 +331,40 @@ public class HexViewTests Assert.Equal (4, hexViewEventArgs.BytesPerLine); Assert.Equal (new (1, 1), hexViewEventArgs.Position); Assert.Equal (5, hexViewEventArgs.Address); - Application.Top.Dispose (); + Application.Current.Dispose (); Application.ResetState (true); } [Fact] public void Source_Sets_Address_To_Zero_If_Greater_Than_Source_Length () { - Application.Navigation = new ApplicationNavigation (); var hv = new HexView (LoadStream (null, out _)) { Width = 10, Height = 5 }; - Application.Top = new Toplevel (); - Application.Top.Add (hv); + Application.Current = new (); + Application.Current.Add (hv); - Application.Top.Layout (); + Application.Current.Layout (); Assert.True (hv.NewKeyDownEvent (Key.End)); Assert.Equal (MEM_STRING_LENGTH, hv.Address); hv.Source = new MemoryStream (); - Application.Top.Layout (); + Application.Current.Layout (); Assert.Equal (0, hv.Address); hv.Source = LoadStream (null, out _); hv.Width = Dim.Fill (); hv.Height = Dim.Fill (); - Application.Top.Layout (); + Application.Current.Layout (); Assert.Equal (0, hv.Address); Assert.True (hv.NewKeyDownEvent (Key.End)); Assert.Equal (MEM_STRING_LENGTH, hv.Address); hv.Source = new MemoryStream (); - Application.Top.Layout (); + Application.Current.Layout (); Assert.Equal (0, hv.Address); - Application.Top.Dispose (); + Application.Current.Dispose (); Application.ResetState (true); } @@ -400,6 +391,7 @@ public class HexViewTests { bArray = Encoding.Default.GetBytes (memString); } + numBytesInMemString = bArray.Length; stream.Write (bArray); @@ -421,8 +413,8 @@ public class HexViewTests } public override void Flush () { baseStream.Flush (); } - public override int Read (byte [] buffer, int offset, int count) { return baseStream.Read (buffer, offset, count); } - public override long Seek (long offset, SeekOrigin origin) { throw new NotImplementedException (); } + public override int Read (byte [] buffer, int offset, int count) => baseStream.Read (buffer, offset, count); + public override long Seek (long offset, SeekOrigin origin) => throw new NotImplementedException (); public override void SetLength (long value) { throw new NotSupportedException (); } public override void Write (byte [] buffer, int offset, int count) { baseStream.Write (buffer, offset, count); } } diff --git a/Tests/UnitTests/Views/LabelTests.cs b/Tests/UnitTests/Views/LabelTests.cs index 550abf360..7b949c459 100644 --- a/Tests/UnitTests/Views/LabelTests.cs +++ b/Tests/UnitTests/Views/LabelTests.cs @@ -1,5 +1,4 @@ -using UnitTests; -using Xunit.Abstractions; +using Xunit.Abstractions; namespace UnitTests.ViewsTests; @@ -112,9 +111,17 @@ public class LabelTests (ITestOutputHelper output) AutoInitShutdownAttribute.RunIteration (); - tf1.Draw (new (new (0, 1), tfSize), label.GetAttributeForRole (VisualRole.Normal), label.GetAttributeForRole (VisualRole.HotNormal)); + tf1.Draw ( + Application.Driver, + new (new (0, 1), tfSize), + label.GetAttributeForRole (VisualRole.Normal), + label.GetAttributeForRole (VisualRole.HotNormal)); - tf2.Draw (new (new (0, 2), tfSize), label.GetAttributeForRole (VisualRole.Normal), label.GetAttributeForRole (VisualRole.HotNormal)); + tf2.Draw ( + Application.Driver, + new (new (0, 2), tfSize), + label.GetAttributeForRole (VisualRole.Normal), + label.GetAttributeForRole (VisualRole.HotNormal)); DriverAssert.AssertDriverContentsWithFrameAre ( @" @@ -135,10 +142,20 @@ This TextFormatter (tf2) with fill will be cleared on rewritten. ", label.Draw (); tf1.Text = "This TextFormatter (tf1) is rewritten."; - tf1.Draw (new (new (0, 1), tfSize), label.GetAttributeForRole (VisualRole.Normal), label.GetAttributeForRole (VisualRole.HotNormal)); + + tf1.Draw ( + Application.Driver, + new (new (0, 1), tfSize), + label.GetAttributeForRole (VisualRole.Normal), + label.GetAttributeForRole (VisualRole.HotNormal)); tf2.Text = "This TextFormatter (tf2) is rewritten."; - tf2.Draw (new (new (0, 2), tfSize), label.GetAttributeForRole (VisualRole.Normal), label.GetAttributeForRole (VisualRole.HotNormal)); + + tf2.Draw ( + Application.Driver, + new (new (0, 2), tfSize), + label.GetAttributeForRole (VisualRole.Normal), + label.GetAttributeForRole (VisualRole.HotNormal)); DriverAssert.AssertDriverContentsWithFrameAre ( @" @@ -301,7 +318,11 @@ e [SetupFakeApplication] public void Full_Border () { - var label = new Label { BorderStyle = LineStyle.Single, Text = "Test" }; + var label = new Label + { + Driver = Application.Driver, + BorderStyle = LineStyle.Single, Text = "Test" + }; label.BeginInit (); label.EndInit (); label.SetRelativeLayout (Application.Screen.Size); @@ -699,6 +720,7 @@ e var label = new Label { Text = "This should be the last line.", + //Width = Dim.Fill (), X = 0, // keep unit test focused; don't use Center here Y = Pos.AnchorEnd (1) @@ -745,6 +767,7 @@ e var label = new Label { Text = "This should be the last line.", + //Width = Dim.Fill (), X = 0, Y = Pos.Bottom (win) @@ -907,7 +930,11 @@ e label.Width = Dim.Fill () - text.Length; label.Height = 0; - var win = new View { CanFocus = true, BorderStyle = LineStyle.Single, Width = Dim.Fill (), Height = Dim.Fill () }; + var win = new View + { + App = ApplicationImpl.Instance, + CanFocus = true, BorderStyle = LineStyle.Single, Width = Dim.Fill (), Height = Dim.Fill () + }; win.Add (label); win.BeginInit (); win.EndInit (); @@ -1071,7 +1098,7 @@ e Assert.Equal (new (5, 1), label.TextFormatter.ConstrainToSize); Assert.Equal (["Label"], label.TextFormatter.GetLines ()); Assert.Equal (new (0, 0, 10, 4), win.Frame); - Assert.Equal (new (0, 0, 10, 4), Application.Top.Frame); + Assert.Equal (new (0, 0, 10, 4), Application.Current.Frame); var expected = @" ┌────────┐ @@ -1130,7 +1157,7 @@ e Assert.Equal (new (5, 1), label.TextFormatter.ConstrainToSize); Assert.Equal (["Label"], label.TextFormatter.GetLines ()); Assert.Equal (new (0, 0, 10, 4), win.Frame); - Assert.Equal (new (0, 0, 10, 4), Application.Top.Frame); + Assert.Equal (new (0, 0, 10, 4), Application.Current.Frame); var expected = @" ┌────────┐ @@ -1202,11 +1229,11 @@ e Text = "nextView", CanFocus = true }; - Application.Navigation = new (); - Application.Top = new (); - Application.Top.Add (otherView, label, nextView); - Application.Top.SetFocus (); + Application.Current = new (); + Application.Current.Add (otherView, label, nextView); + + Application.Current.SetFocus (); Assert.True (otherView.HasFocus); Assert.True (Application.RaiseKeyDownEvent (label.HotKey)); @@ -1214,7 +1241,7 @@ e Assert.False (label.HasFocus); Assert.True (nextView.HasFocus); - Application.Top.Dispose (); + Application.Current.Dispose (); Application.ResetState (); } @@ -1224,19 +1251,18 @@ e View otherView = new () { X = 0, Y = 0, Width = 1, Height = 1, Id = "otherView", CanFocus = true }; Label label = new () { X = 0, Y = 1, Text = "_label" }; View nextView = new () { X = Pos.Right (label), Y = Pos.Top (label), Width = 1, Height = 1, Id = "nextView", CanFocus = true }; - Application.Navigation = new (); - Application.Top = new (); - Application.Top.Add (otherView, label, nextView); - Application.Top.Layout (); + Application.Current = new (); + Application.Current.Add (otherView, label, nextView); + Application.Current.Layout (); - Application.Top.SetFocus (); + Application.Current.SetFocus (); // click on label Application.RaiseMouseEvent (new () { ScreenPosition = label.Frame.Location, Flags = MouseFlags.Button1Clicked }); Assert.False (label.HasFocus); Assert.True (nextView.HasFocus); - Application.Top.Dispose (); + Application.Current.Dispose (); Application.ResetState (); } @@ -1254,9 +1280,9 @@ e Text = "view", CanFocus = true }; - Application.Navigation = new (); - Application.Top = new (); - Application.Top.Add (label, view); + + Application.Current = new (); + Application.Current.Add (label, view); view.SetFocus (); Assert.True (label.CanFocus); @@ -1269,15 +1295,13 @@ e Assert.True (label.HasFocus); Assert.False (view.HasFocus); - Application.Top.Dispose (); + Application.Current.Dispose (); Application.ResetState (); } [Fact] public void CanFocus_True_MouseClick_Focuses () { - Application.Navigation = new (); - Label label = new () { Text = "label", @@ -1296,14 +1320,14 @@ e CanFocus = true }; - Application.Top = new () + Application.Current = new () { Width = 10, Height = 10 }; - Application.Top.Add (label, otherView); - Application.Top.SetFocus (); - Application.Top.Layout (); + Application.Current.Add (label, otherView); + Application.Current.SetFocus (); + Application.Current.Layout (); Assert.True (label.CanFocus); Assert.True (label.HasFocus); @@ -1323,7 +1347,7 @@ e Assert.False (label.HasFocus); Assert.True (otherView.HasFocus); - Application.Top.Dispose (); + Application.Current.Dispose (); Application.ResetState (); } diff --git a/Tests/UnitTests/Views/MenuBarTests.cs b/Tests/UnitTests/Views/MenuBarTests.cs index 8ad5f05eb..b77ebc1ad 100644 --- a/Tests/UnitTests/Views/MenuBarTests.cs +++ b/Tests/UnitTests/Views/MenuBarTests.cs @@ -10,18 +10,33 @@ public class MenuBarTests () public void DefaultKey_Activates_And_Opens () { // Arrange + var top = new Toplevel () + { + App = ApplicationImpl.Instance + }; + + var menuBar = new MenuBarv2 () { Id = "menuBar" }; + top.Add (menuBar); + var menuItem = new MenuItemv2 { Id = "menuItem", Title = "_Item" }; var menu = new Menuv2 ([menuItem]) { Id = "menu" }; var menuBarItem = new MenuBarItemv2 { Id = "menuBarItem", Title = "_New" }; var menuBarItemPopover = new PopoverMenu (); + + menuBar.Add (menuBarItem); menuBarItem.PopoverMenu = menuBarItemPopover; menuBarItemPopover.Root = menu; - var menuBar = new MenuBarv2 () { Id = "menuBar" }; - menuBar.Add (menuBarItem); + + + Assert.NotNull (menuBar.App); + Assert.NotNull (menu.App); + Assert.NotNull (menuItem.App); + Assert.NotNull (menuBarItem); + Assert.NotNull (menuBarItemPopover); + Assert.Single (menuBar.SubViews); Assert.Single (menuBarItem.SubViews); - var top = new Toplevel (); - top.Add (menuBar); + SessionToken rs = Application.Begin (top); Assert.False (menuBar.Active); @@ -43,17 +58,10 @@ public class MenuBarTests () public void DefaultKey_Deactivates () { // Arrange - var menuItem = new MenuItemv2 { Id = "menuItem", Title = "_Item" }; - var menu = new Menuv2 ([menuItem]) { Id = "menu" }; - var menuBarItem = new MenuBarItemv2 { Id = "menuBarItem", Title = "_New" }; - var menuBarItemPopover = new PopoverMenu (); - menuBarItem.PopoverMenu = menuBarItemPopover; - menuBarItemPopover.Root = menu; - var menuBar = new MenuBarv2 () { Id = "menuBar" }; - menuBar.Add (menuBarItem); - Assert.Single (menuBar.SubViews); - Assert.Single (menuBarItem.SubViews); - var top = new Toplevel (); + var top = new Toplevel () { App = ApplicationImpl.Instance }; + MenuBarv2 menuBar = new MenuBarv2 () { App = ApplicationImpl.Instance }; + menuBar.EnableForDesign (ref top); + top.Add (menuBar); SessionToken rs = Application.Begin (top); Assert.False (menuBar.Active); @@ -61,15 +69,12 @@ public class MenuBarTests () // Act Application.RaiseKeyDownEvent (MenuBarv2.DefaultKey); Assert.True (menuBar.IsOpen ()); - Assert.True (menuBarItem.PopoverMenu.Visible); Application.RaiseKeyDownEvent (MenuBarv2.DefaultKey); Assert.False (menuBar.Active); Assert.False (menuBar.IsOpen ()); Assert.False (menuBar.HasFocus); Assert.False (menuBar.CanFocus); - Assert.False (menuBarItem.PopoverMenu.Visible); - Assert.False (menuBarItem.PopoverMenu.HasFocus); Application.End (rs); top.Dispose (); @@ -377,17 +382,10 @@ public class MenuBarTests () public void Mouse_Click_Activates_And_Opens () { // Arrange - var menuItem = new MenuItemv2 { Id = "menuItem", Title = "_Item" }; - var menu = new Menuv2 ([menuItem]) { Id = "menu" }; - var menuBarItem = new MenuBarItemv2 { Id = "menuBarItem", Title = "_New" }; - var menuBarItemPopover = new PopoverMenu (); - menuBarItem.PopoverMenu = menuBarItemPopover; - menuBarItemPopover.Root = menu; - var menuBar = new MenuBarv2 () { Id = "menuBar" }; - menuBar.Add (menuBarItem); - Assert.Single (menuBar.SubViews); - Assert.Single (menuBarItem.SubViews); - var top = new Toplevel (); + var top = new Toplevel () { App = ApplicationImpl.Instance }; + MenuBarv2 menuBar = new MenuBarv2 () { App = ApplicationImpl.Instance }; + menuBar.EnableForDesign (ref top); + top.Add (menuBar); SessionToken rs = Application.Begin (top); Assert.False (menuBar.Active); @@ -401,8 +399,6 @@ public class MenuBarTests () Assert.True (menuBar.IsOpen ()); Assert.True (menuBar.HasFocus); Assert.True (menuBar.CanFocus); - Assert.True (menuBarItem.PopoverMenu.Visible); - Assert.True (menuBarItem.PopoverMenu.HasFocus); Application.End (rs); top.Dispose (); @@ -483,7 +479,7 @@ public class MenuBarTests () Application.RaiseKeyDownEvent (Key.N.WithAlt); Assert.Equal (0, action); - Assert.Equal(Key.I, menuItem.HotKey); + Assert.Equal (Key.I, menuItem.HotKey); Application.RaiseKeyDownEvent (Key.I); Assert.Equal (1, action); Assert.False (menuBar.Active); diff --git a/Tests/UnitTests/Views/Menuv1/MenuBarv1Tests.cs b/Tests/UnitTests/Views/Menuv1/MenuBarv1Tests.cs index 31ec42bde..1241716ca 100644 --- a/Tests/UnitTests/Views/Menuv1/MenuBarv1Tests.cs +++ b/Tests/UnitTests/Views/Menuv1/MenuBarv1Tests.cs @@ -698,7 +698,7 @@ public class MenuBarv1Tests (ITestOutputHelper output) Dialog.DefaultShadow = ShadowStyle.None; Button.DefaultShadow = ShadowStyle.None; - Assert.Equal (new (0, 0, 40, 15), View.GetClip ()!.GetBounds ()); + Assert.Equal (new (0, 0, 40, 15), Application.Current!.GetClip ()!.GetBounds ()); DriverAssert.AssertDriverContentsWithFrameAre (@"", output); List items = new () @@ -1100,11 +1100,11 @@ wo Assert.False (copyAction); #if SUPPORT_ALT_TO_ACTIVATE_MENU - Assert.False (Application.Top.ProcessKeyDown (new KeyEventArgs (Key.AltMask))); - Assert.False (Application.Top.ProcessKeyDown (new KeyEventArgs (Key.AltMask))); - Assert.True (Application.Top.ProcessKeyUp (new KeyEventArgs (Key.AltMask))); + Assert.False (Application.Current.ProcessKeyDown (new KeyEventArgs (Key.AltMask))); + Assert.False (Application.Current.ProcessKeyDown (new KeyEventArgs (Key.AltMask))); + Assert.True (Application.Current.ProcessKeyUp (new KeyEventArgs (Key.AltMask))); Assert.True (menu.IsMenuOpen); - Application.Top.Draw (); + Application.Current.Draw (); string expected = @" File Edit @@ -1113,26 +1113,26 @@ wo var pos = DriverAsserts.AssertDriverContentsWithFrameAre (expected, output); Assert.Equal (new (1, 0, 11, 1), pos); - Assert.True (Application.Top.ProcessKeyDown (new KeyEventArgs (Key.N))); + Assert.True (Application.Current.ProcessKeyDown (new KeyEventArgs (Key.N))); AutoInitShutdownAttribute.RunIteration (); Assert.False (newAction); // not yet, hot keys don't work if the item is not visible - Assert.True (Application.Top.ProcessKeyDown (new KeyEventArgs (Key.F))); + Assert.True (Application.Current.ProcessKeyDown (new KeyEventArgs (Key.F))); AutoInitShutdownAttribute.RunIteration (); - Assert.True (Application.Top.ProcessKeyDown (new KeyEventArgs (Key.N))); + Assert.True (Application.Current.ProcessKeyDown (new KeyEventArgs (Key.N))); AutoInitShutdownAttribute.RunIteration (); Assert.True (newAction); - Application.Top.Draw (); + Application.Current.Draw (); expected = @" File Edit "; - Assert.False (Application.Top.ProcessKeyDown (new KeyEventArgs (Key.AltMask))); - Assert.True (Application.Top.ProcessKeyUp (new KeyEventArgs (Key.AltMask))); - Assert.True (Application.Top.ProcessKeyUp (new KeyEventArgs (Key.AltMask))); + Assert.False (Application.Current.ProcessKeyDown (new KeyEventArgs (Key.AltMask))); + Assert.True (Application.Current.ProcessKeyUp (new KeyEventArgs (Key.AltMask))); + Assert.True (Application.Current.ProcessKeyUp (new KeyEventArgs (Key.AltMask))); Assert.True (menu.IsMenuOpen); - Application.Top.Draw (); + Application.Current.Draw (); expected = @" File Edit @@ -1141,8 +1141,8 @@ wo pos = DriverAsserts.AssertDriverContentsWithFrameAre (expected, output); Assert.Equal (new (1, 0, 11, 1), pos); - Assert.True (Application.Top.ProcessKeyDown (new KeyEventArgs (Key.CursorRight))); - Assert.True (Application.Top.ProcessKeyDown (new KeyEventArgs (Key.C))); + Assert.True (Application.Current.ProcessKeyDown (new KeyEventArgs (Key.CursorRight))); + Assert.True (Application.Current.ProcessKeyDown (new KeyEventArgs (Key.C))); AutoInitShutdownAttribute.RunIteration (); Assert.True (copyAction); #endif @@ -1211,19 +1211,19 @@ wo Assert.True (menu.NewKeyDownEvent (Key.F.WithAlt)); Assert.True (menu.IsMenuOpen); - Application.Top.Draw (); + Application.Current.Draw (); DriverAssert.AssertDriverContentsAre (expectedMenu.ExpectedSubMenuOpen (0), output); - Assert.True (Application.Top.SubViews.ElementAt (1).NewKeyDownEvent (Key.N)); + Assert.True (Application.Current.SubViews.ElementAt (1).NewKeyDownEvent (Key.N)); AutoInitShutdownAttribute.RunIteration (); Assert.True (newAction); Assert.True (menu.NewKeyDownEvent (Key.E.WithAlt)); Assert.True (menu.IsMenuOpen); - Application.Top.Draw (); + Application.Current.Draw (); DriverAssert.AssertDriverContentsAre (expectedMenu.ExpectedSubMenuOpen (1), output); - Assert.True (Application.Top.SubViews.ElementAt (1).NewKeyDownEvent (Key.C)); + Assert.True (Application.Current.SubViews.ElementAt (1).NewKeyDownEvent (Key.C)); AutoInitShutdownAttribute.RunIteration (); Assert.True (copyAction); top.Dispose (); @@ -1594,7 +1594,7 @@ wo Assert.True (menu.NewMouseEvent (new () { Position = new (1, 0), Flags = MouseFlags.Button1Pressed, View = menu })); Assert.False (menu.IsMenuOpen); - View.SetClipToScreen (); + top.SetClipToScreen (); top.Draw (); DriverAssert.AssertDriverContentsAre (expectedMenu.ClosedMenuText, output); top.Dispose (); @@ -1692,7 +1692,7 @@ wo ); Assert.True (menu._openMenu.NewKeyDownEvent (Key.CursorRight)); - View.SetClipToScreen (); + top.SetClipToScreen (); top.Draw (); DriverAssert.AssertDriverContentsWithFrameAre ( @@ -1804,7 +1804,7 @@ wo ); Assert.True (menu._openMenu.NewKeyDownEvent (Key.CursorRight)); - View.SetClipToScreen (); + top.SetClipToScreen (); top.Draw (); DriverAssert.AssertDriverContentsWithFrameAre ( @@ -1945,7 +1945,7 @@ wo Application.AddTimeout (TimeSpan.Zero, () => { - Toplevel top = Application.Top; + Toplevel top = Application.Current; AutoInitShutdownAttribute.RunIteration (); @@ -2015,7 +2015,7 @@ wo Assert.True ( ((MenuBar)top.SubViews.ElementAt (0))._openMenu.NewKeyDownEvent (Key.CursorRight) ); - View.SetClipToScreen (); + top.SetClipToScreen (); top.Draw (); DriverAssert.AssertDriverContentsWithFrameAre ( @@ -2090,16 +2090,16 @@ wo DriverAssert.AssertDriverContentsAre (expectedMenu.ExpectedSubMenuOpen (0), output); // Open second - Assert.True (Application.Top.SubViews.ElementAt (1).NewKeyDownEvent (Key.CursorRight)); + Assert.True (Application.Current.SubViews.ElementAt (1).NewKeyDownEvent (Key.CursorRight)); Assert.True (menu.IsMenuOpen); - View.SetClipToScreen (); + top.SetClipToScreen (); top.Draw (); DriverAssert.AssertDriverContentsAre (expectedMenu.ExpectedSubMenuOpen (1), output); // Close menu Assert.True (menu.NewKeyDownEvent (menu.Key)); Assert.False (menu.IsMenuOpen); - View.SetClipToScreen (); + top.SetClipToScreen (); top.Draw (); DriverAssert.AssertDriverContentsAre (expectedMenu.ClosedMenuText, output); @@ -2133,21 +2133,21 @@ wo // Open first Assert.True (menu.NewKeyDownEvent (menu.Key)); Assert.True (menu.IsMenuOpen); - View.SetClipToScreen (); + top.SetClipToScreen (); top.Draw (); DriverAssert.AssertDriverContentsAre (expectedMenu.ExpectedSubMenuOpen (0), output); // Open second Assert.True (top.SubViews.ElementAt (1).NewKeyDownEvent (Key.CursorRight)); Assert.True (menu.IsMenuOpen); - View.SetClipToScreen (); - Application.Top.Draw (); + top.SetClipToScreen (); + Application.Current.Draw (); DriverAssert.AssertDriverContentsAre (expectedMenu.ExpectedSubMenuOpen (1), output); // Close menu Assert.True (menu.NewKeyDownEvent (menu.Key)); Assert.False (menu.IsMenuOpen); - View.SetClipToScreen (); + top.SetClipToScreen (); top.Draw (); DriverAssert.AssertDriverContentsAre (expectedMenu.ClosedMenuText, output); top.Dispose (); @@ -2216,7 +2216,7 @@ wo top.Add (menu); Application.Begin (top); - View.SetClipToScreen (); + top.SetClipToScreen (); top.Draw (); DriverAssert.AssertDriverContentsAre (expectedMenu.ClosedMenuText, output); @@ -2224,7 +2224,7 @@ wo { menu.OpenMenu (i); Assert.True (menu.IsMenuOpen); - View.SetClipToScreen (); + top.SetClipToScreen (); top.Draw (); DriverAssert.AssertDriverContentsAre (expectedMenu.ExpectedSubMenuOpen (i), output); } @@ -2483,7 +2483,7 @@ Edit Assert.True (menu.NewKeyDownEvent (menu.Key)); Assert.True (menu.IsMenuOpen); Assert.False (isMenuClosed); - View.SetClipToScreen (); + top.SetClipToScreen (); top.Draw (); expected = @" @@ -2498,7 +2498,7 @@ Edit Assert.True (menu.NewKeyDownEvent (menu.Key)); Assert.False (menu.IsMenuOpen); Assert.True (isMenuClosed); - View.SetClipToScreen (); + top.SetClipToScreen (); top.Draw (); expected = @" @@ -2655,7 +2655,7 @@ Edit Assert.Equal (1, menu._selected); Assert.Equal (-1, menu._selectedSub); Assert.Null (menu._openSubMenu); - View.SetClipToScreen (); + top.SetClipToScreen (); top.Draw (); DriverAssert.AssertDriverContentsAre (expectedMenu.ExpectedSubMenuOpen (1), output); @@ -2663,7 +2663,7 @@ Edit Assert.True (menu._openMenu.NewKeyDownEvent (Key.CursorRight)); Assert.True (menu.IsMenuOpen); Assert.False (tf.HasFocus); - View.SetClipToScreen (); + top.SetClipToScreen (); top.Draw (); DriverAssert.AssertDriverContentsAre (expectedMenu.ExpectedSubMenuOpen (2), output); @@ -2671,21 +2671,21 @@ Edit Assert.True (menu._openMenu.NewKeyDownEvent (Key.CursorLeft)); Assert.True (menu.IsMenuOpen); Assert.False (tf.HasFocus); - View.SetClipToScreen (); + top.SetClipToScreen (); top.Draw (); DriverAssert.AssertDriverContentsAre (expectedMenu.ExpectedSubMenuOpen (1), output); Assert.True (menu._openMenu.NewKeyDownEvent (Key.CursorLeft)); Assert.True (menu.IsMenuOpen); Assert.False (tf.HasFocus); - View.SetClipToScreen (); + top.SetClipToScreen (); top.Draw (); DriverAssert.AssertDriverContentsAre (expectedMenu.ExpectedSubMenuOpen (0), output); Assert.True (Application.RaiseKeyDownEvent (menu.Key)); Assert.False (menu.IsMenuOpen); Assert.True (tf.HasFocus); - View.SetClipToScreen (); + top.SetClipToScreen (); top.Draw (); DriverAssert.AssertDriverContentsAre (expectedMenu.ClosedMenuText, output); top.Dispose (); @@ -2756,7 +2756,7 @@ Edit ); Assert.True (menu.IsMenuOpen); Assert.False (tf.HasFocus); - View.SetClipToScreen (); + top.SetClipToScreen (); top.Draw (); DriverAssert.AssertDriverContentsAre (expectedMenu.ExpectedSubMenuOpen (1), output); @@ -2767,7 +2767,7 @@ Edit ); Assert.True (menu.IsMenuOpen); Assert.False (tf.HasFocus); - View.SetClipToScreen (); + top.SetClipToScreen (); top.Draw (); DriverAssert.AssertDriverContentsAre (expectedMenu.ExpectedSubMenuOpen (2), output); @@ -2778,7 +2778,7 @@ Edit ); Assert.True (menu.IsMenuOpen); Assert.False (tf.HasFocus); - View.SetClipToScreen (); + top.SetClipToScreen (); top.Draw (); DriverAssert.AssertDriverContentsAre (expectedMenu.ClosedMenuText, output); @@ -2789,14 +2789,14 @@ Edit ); Assert.True (menu.IsMenuOpen); Assert.False (tf.HasFocus); - View.SetClipToScreen (); + top.SetClipToScreen (); top.Draw (); DriverAssert.AssertDriverContentsAre (expectedMenu.ExpectedSubMenuOpen (0), output); Assert.True (menu.NewMouseEvent (new () { Position = new (8, 0), Flags = MouseFlags.Button1Pressed, View = menu })); Assert.False (menu.IsMenuOpen); Assert.True (tf.HasFocus); - View.SetClipToScreen (); + top.SetClipToScreen (); top.Draw (); DriverAssert.AssertDriverContentsAre (expectedMenu.ClosedMenuText, output); top.Dispose (); @@ -3075,7 +3075,7 @@ Edit pos = DriverAssert.AssertDriverContentsWithFrameAre (expected, output); - Assert.True (Application.Top.SubViews.ElementAt (1).NewKeyDownEvent (Key.CursorDown)); + Assert.True (Application.Current.SubViews.ElementAt (1).NewKeyDownEvent (Key.CursorDown)); top.Draw (); expected = @" @@ -3090,7 +3090,7 @@ Edit pos = DriverAssert.AssertDriverContentsWithFrameAre (expected, output); - Assert.True (Application.Top.SubViews.ElementAt (2).NewKeyDownEvent (Key.CursorLeft)); + Assert.True (Application.Current.SubViews.ElementAt (2).NewKeyDownEvent (Key.CursorLeft)); top.Draw (); expected = @" @@ -3104,7 +3104,7 @@ Edit pos = DriverAssert.AssertDriverContentsWithFrameAre (expected, output); - Assert.True (Application.Top.SubViews.ElementAt (1).NewKeyDownEvent (Key.Esc)); + Assert.True (Application.Current.SubViews.ElementAt (1).NewKeyDownEvent (Key.Esc)); top.Draw (); expected = @" @@ -3186,7 +3186,7 @@ Edit menu.NewMouseEvent ( new () { - Position = new (1, 2), Flags = MouseFlags.ReportMousePosition, View = Application.Top.SubViews.ElementAt (1) + Position = new (1, 2), Flags = MouseFlags.ReportMousePosition, View = Application.Current.SubViews.ElementAt (1) } ); top.Draw (); @@ -3208,7 +3208,7 @@ Edit menu.NewMouseEvent ( new () { - Position = new (1, 1), Flags = MouseFlags.ReportMousePosition, View = Application.Top.SubViews.ElementAt (1) + Position = new (1, 1), Flags = MouseFlags.ReportMousePosition, View = Application.Current.SubViews.ElementAt (1) } ) ); @@ -3227,7 +3227,7 @@ Edit Assert.Equal (new (1, 0, 10, 6), pos); menu.NewMouseEvent ( - new () { Position = new (70, 2), Flags = MouseFlags.Button1Clicked, View = Application.Top } + new () { Position = new (70, 2), Flags = MouseFlags.Button1Clicked, View = Application.Current } ); top.Draw (); diff --git a/Tests/UnitTests/Views/ProgressBarTests.cs b/Tests/UnitTests/Views/ProgressBarTests.cs index 26b70fe0e..b7bb6aaa2 100644 --- a/Tests/UnitTests/Views/ProgressBarTests.cs +++ b/Tests/UnitTests/Views/ProgressBarTests.cs @@ -26,9 +26,13 @@ public class ProgressBarTests [AutoInitShutdown] public void Fraction_Redraw () { - var driver = Application.Driver; + var driver = ApplicationImpl.Instance.Driver; - var pb = new ProgressBar { Width = 5 }; + var pb = new ProgressBar + { + Driver = driver, + Width = 5 + }; pb.BeginInit (); pb.EndInit (); @@ -37,7 +41,7 @@ public class ProgressBarTests for (var i = 0; i <= pb.Frame.Width; i++) { pb.Fraction += 0.2F; - View.SetClipToScreen (); + pb.SetClipToScreen (); pb.Draw (); if (i == 0) @@ -161,10 +165,11 @@ public class ProgressBarTests [AutoInitShutdown] public void Pulse_Redraw_BidirectionalMarquee_False () { - var driver = Application.Driver; + var driver = ApplicationImpl.Instance.Driver; var pb = new ProgressBar { + Driver = driver, Width = 15, ProgressBarStyle = ProgressBarStyle.MarqueeBlocks, BidirectionalMarquee = false }; @@ -175,7 +180,7 @@ public class ProgressBarTests for (var i = 0; i < 38; i++) { pb.Pulse (); - View.SetClipToScreen (); + pb.SetClipToScreen (); pb.Draw (); if (i == 0) @@ -869,9 +874,13 @@ public class ProgressBarTests [AutoInitShutdown] public void Pulse_Redraw_BidirectionalMarquee_True_Default () { - var driver = Application.Driver; + var driver = ApplicationImpl.Instance.Driver; - var pb = new ProgressBar { Width = 15, ProgressBarStyle = ProgressBarStyle.MarqueeBlocks }; + var pb = new ProgressBar + { + Driver = driver, + Width = 15, ProgressBarStyle = ProgressBarStyle.MarqueeBlocks + }; pb.BeginInit (); pb.EndInit (); @@ -880,7 +889,7 @@ public class ProgressBarTests for (var i = 0; i < 38; i++) { pb.Pulse (); - View.SetClipToScreen (); + pb.SetClipToScreen (); pb.Draw (); if (i == 0) diff --git a/Tests/UnitTests/Views/ScrollBarTests.cs b/Tests/UnitTests/Views/ScrollBarTests.cs index 9d0433832..4a5cdf7ad 100644 --- a/Tests/UnitTests/Views/ScrollBarTests.cs +++ b/Tests/UnitTests/Views/ScrollBarTests.cs @@ -495,6 +495,8 @@ public class ScrollBarTests (ITestOutputHelper output) { var super = new Window { + Driver = ApplicationImpl.Instance.Driver, + Id = "super", Width = width + 2, Height = height + 2, diff --git a/Tests/UnitTests/Views/ScrollSliderTests.cs b/Tests/UnitTests/Views/ScrollSliderTests.cs deleted file mode 100644 index 5397b16b1..000000000 --- a/Tests/UnitTests/Views/ScrollSliderTests.cs +++ /dev/null @@ -1,340 +0,0 @@ -using UnitTests; -using Xunit.Abstractions; - -namespace UnitTests.ViewsTests; - -public class ScrollSliderTests (ITestOutputHelper output) -{ - [Theory] - [SetupFakeApplication] - [InlineData ( - 3, - 10, - 1, - 0, - Orientation.Vertical, - @" -┌───┐ -│███│ -│ │ -│ │ -│ │ -│ │ -│ │ -│ │ -│ │ -│ │ -│ │ -└───┘")] - [InlineData ( - 10, - 1, - 3, - 0, - Orientation.Horizontal, - @" -┌──────────┐ -│███ │ -└──────────┘")] - [InlineData ( - 3, - 10, - 3, - 0, - Orientation.Vertical, - @" -┌───┐ -│███│ -│███│ -│███│ -│ │ -│ │ -│ │ -│ │ -│ │ -│ │ -│ │ -└───┘")] - - - - [InlineData ( - 3, - 10, - 5, - 0, - Orientation.Vertical, - @" -┌───┐ -│███│ -│███│ -│███│ -│███│ -│███│ -│ │ -│ │ -│ │ -│ │ -│ │ -└───┘")] - - [InlineData ( - 3, - 10, - 5, - 1, - Orientation.Vertical, - @" -┌───┐ -│ │ -│███│ -│███│ -│███│ -│███│ -│███│ -│ │ -│ │ -│ │ -│ │ -└───┘")] - [InlineData ( - 3, - 10, - 5, - 4, - Orientation.Vertical, - @" -┌───┐ -│ │ -│ │ -│ │ -│ │ -│███│ -│███│ -│███│ -│███│ -│███│ -│ │ -└───┘")] - [InlineData ( - 3, - 10, - 5, - 5, - Orientation.Vertical, - @" -┌───┐ -│ │ -│ │ -│ │ -│ │ -│ │ -│███│ -│███│ -│███│ -│███│ -│███│ -└───┘")] - [InlineData ( - 3, - 10, - 5, - 6, - Orientation.Vertical, - @" -┌───┐ -│ │ -│ │ -│ │ -│ │ -│ │ -│███│ -│███│ -│███│ -│███│ -│███│ -└───┘")] - - [InlineData ( - 3, - 10, - 10, - 0, - Orientation.Vertical, - @" -┌───┐ -│███│ -│███│ -│███│ -│███│ -│███│ -│███│ -│███│ -│███│ -│███│ -│███│ -└───┘")] - - [InlineData ( - 3, - 10, - 10, - 5, - Orientation.Vertical, - @" -┌───┐ -│███│ -│███│ -│███│ -│███│ -│███│ -│███│ -│███│ -│███│ -│███│ -│███│ -└───┘")] - [InlineData ( - 3, - 10, - 11, - 0, - Orientation.Vertical, - @" -┌───┐ -│███│ -│███│ -│███│ -│███│ -│███│ -│███│ -│███│ -│███│ -│███│ -│███│ -└───┘")] - - [InlineData ( - 10, - 3, - 5, - 0, - Orientation.Horizontal, - @" -┌──────────┐ -│█████ │ -│█████ │ -│█████ │ -└──────────┘")] - - [InlineData ( - 10, - 3, - 5, - 1, - Orientation.Horizontal, - @" -┌──────────┐ -│ █████ │ -│ █████ │ -│ █████ │ -└──────────┘")] - [InlineData ( - 10, - 3, - 5, - 4, - Orientation.Horizontal, - @" -┌──────────┐ -│ █████ │ -│ █████ │ -│ █████ │ -└──────────┘")] - [InlineData ( - 10, - 3, - 5, - 5, - Orientation.Horizontal, - @" -┌──────────┐ -│ █████│ -│ █████│ -│ █████│ -└──────────┘")] - [InlineData ( - 10, - 3, - 5, - 6, - Orientation.Horizontal, - @" -┌──────────┐ -│ █████│ -│ █████│ -│ █████│ -└──────────┘")] - - [InlineData ( - 10, - 3, - 10, - 0, - Orientation.Horizontal, - @" -┌──────────┐ -│██████████│ -│██████████│ -│██████████│ -└──────────┘")] - - [InlineData ( - 10, - 3, - 10, - 5, - Orientation.Horizontal, - @" -┌──────────┐ -│██████████│ -│██████████│ -│██████████│ -└──────────┘")] - [InlineData ( - 10, - 3, - 11, - 0, - Orientation.Horizontal, - @" -┌──────────┐ -│██████████│ -│██████████│ -│██████████│ -└──────────┘")] - public void Draws_Correctly (int superViewportWidth, int superViewportHeight, int sliderSize, int position, Orientation orientation, string expected) - { - var super = new Window - { - Id = "super", - Width = superViewportWidth + 2, - Height = superViewportHeight + 2 - }; - - var scrollSlider = new ScrollSlider - { - Orientation = orientation, - Size = sliderSize, - //Position = position, - }; - Assert.Equal (sliderSize, scrollSlider.Size); - super.Add (scrollSlider); - scrollSlider.Position = position; - - super.Layout (); - super.Draw (); - - _ = DriverAssert.AssertDriverContentsWithFrameAre (expected, output); - } -} diff --git a/Tests/UnitTests/Views/ShortcutTests.cs b/Tests/UnitTests/Views/ShortcutTests.cs index 09b4e66d1..79da2dbc6 100644 --- a/Tests/UnitTests/Views/ShortcutTests.cs +++ b/Tests/UnitTests/Views/ShortcutTests.cs @@ -23,7 +23,7 @@ public class ShortcutTests [InlineData (9, 0)] public void MouseClick_Raises_Accepted (int x, int expectedAccepted) { - Application.Top = new (); + Application.Current = new (); var shortcut = new Shortcut { @@ -31,8 +31,8 @@ public class ShortcutTests Text = "0", Title = "C" }; - Application.Top.Add (shortcut); - Application.Top.Layout (); + Application.Current.Add (shortcut); + Application.Current.Layout (); var accepted = 0; shortcut.Accepting += (s, e) => accepted++; @@ -46,7 +46,7 @@ public class ShortcutTests Assert.Equal (expectedAccepted, accepted); - Application.Top.Dispose (); + Application.Current.Dispose (); Application.ResetState (true); } @@ -74,7 +74,7 @@ public class ShortcutTests int expectedShortcutSelected ) { - Application.Top = new (); + Application.Current = new (); var shortcut = new Shortcut { @@ -93,9 +93,9 @@ public class ShortcutTests var shortcutSelectCount = 0; shortcut.Selecting += (s, e) => { shortcutSelectCount++; }; - Application.Top.Add (shortcut); - Application.Top.SetRelativeLayout (new (100, 100)); - Application.Top.LayoutSubViews (); + Application.Current.Add (shortcut); + Application.Current.SetRelativeLayout (new (100, 100)); + Application.Current.LayoutSubViews (); Application.RaiseMouseEvent ( new () @@ -109,7 +109,7 @@ public class ShortcutTests Assert.Equal (expectedCommandViewAccepted, commandViewAcceptCount); Assert.Equal (expectedCommandViewSelected, commandViewSelectCount); - Application.Top.Dispose (); + Application.Current.Dispose (); Application.ResetState (true); } @@ -130,7 +130,7 @@ public class ShortcutTests [InlineData (9, 0, 0)] public void MouseClick_Button_CommandView_Raises_Shortcut_Accepted (int mouseX, int expectedAccept, int expectedButtonAccept) { - Application.Top = new (); + Application.Current = new (); var shortcut = new Shortcut { @@ -147,9 +147,9 @@ public class ShortcutTests }; var buttonAccepted = 0; shortcut.CommandView.Accepting += (s, e) => { buttonAccepted++; }; - Application.Top.Add (shortcut); - Application.Top.SetRelativeLayout (new (100, 100)); - Application.Top.LayoutSubViews (); + Application.Current.Add (shortcut); + Application.Current.SetRelativeLayout (new (100, 100)); + Application.Current.LayoutSubViews (); var accepted = 0; shortcut.Accepting += (s, e) => { accepted++; }; @@ -164,7 +164,7 @@ public class ShortcutTests Assert.Equal (expectedAccept, accepted); Assert.Equal (expectedButtonAccept, buttonAccepted); - Application.Top.Dispose (); + Application.Current.Dispose (); Application.ResetState (true); } @@ -186,7 +186,7 @@ public class ShortcutTests [InlineData (10, 1, 0)] public void MouseClick_CheckBox_CommandView_Raises_Shortcut_Accepted_Selected_Correctly (int mouseX, int expectedAccepted, int expectedCheckboxAccepted) { - Application.Top = new (); + Application.Current = new (); var shortcut = new Shortcut { @@ -212,9 +212,9 @@ public class ShortcutTests checkboxSelected++; }; - Application.Top.Add (shortcut); - Application.Top.SetRelativeLayout (new (100, 100)); - Application.Top.LayoutSubViews (); + Application.Current.Add (shortcut); + Application.Current.SetRelativeLayout (new (100, 100)); + Application.Current.LayoutSubViews (); var selected = 0; shortcut.Selecting += (s, e) => @@ -241,7 +241,7 @@ public class ShortcutTests Assert.Equal (expectedCheckboxAccepted, checkboxAccepted); Assert.Equal (expectedCheckboxAccepted, checkboxSelected); - Application.Top.Dispose (); + Application.Current.Dispose (); Application.ResetState (true); } @@ -260,7 +260,7 @@ public class ShortcutTests [InlineData (false, KeyCode.F1, 0, 0)] public void KeyDown_Raises_Accepted_Selected (bool canFocus, KeyCode key, int expectedAccept, int expectedSelect) { - Application.Top = new (); + Application.Current = new (); var shortcut = new Shortcut { @@ -269,7 +269,7 @@ public class ShortcutTests Title = "_C", CanFocus = canFocus }; - Application.Top.Add (shortcut); + Application.Current.Add (shortcut); shortcut.SetFocus (); Assert.Equal (canFocus, shortcut.HasFocus); @@ -285,7 +285,7 @@ public class ShortcutTests Assert.Equal (expectedAccept, accepted); Assert.Equal (expectedSelect, selected); - Application.Top.Dispose (); + Application.Current.Dispose (); Application.ResetState (true); } @@ -305,7 +305,7 @@ public class ShortcutTests [InlineData (false, KeyCode.F1, 0, 0)] public void KeyDown_CheckBox_Raises_Accepted_Selected (bool canFocus, KeyCode key, int expectedAccept, int expectedSelect) { - Application.Top = new (); + Application.Current = new (); var shortcut = new Shortcut { @@ -317,7 +317,7 @@ public class ShortcutTests }, CanFocus = canFocus }; - Application.Top.Add (shortcut); + Application.Current.Add (shortcut); shortcut.SetFocus (); Assert.Equal (canFocus, shortcut.HasFocus); @@ -337,7 +337,7 @@ public class ShortcutTests Assert.Equal (expectedAccept, accepted); Assert.Equal (expectedSelect, selected); - Application.Top.Dispose (); + Application.Current.Dispose (); Application.ResetState (true); } [Theory] @@ -349,17 +349,17 @@ public class ShortcutTests [InlineData (KeyCode.F1, 0)] public void KeyDown_App_Scope_Invokes_Accept (KeyCode key, int expectedAccept) { - Application.Top = new (); + Application.Current = new () { App = Application.Create () }; var shortcut = new Shortcut { Key = Key.A, - BindKeyToApplication = true, Text = "0", Title = "_C" }; - Application.Top.Add (shortcut); - Application.Top.SetFocus (); + Application.Current.Add (shortcut); + shortcut.BindKeyToApplication = true; + Application.Current.SetFocus (); var accepted = 0; shortcut.Accepting += (s, e) => accepted++; @@ -368,7 +368,7 @@ public class ShortcutTests Assert.Equal (expectedAccept, accepted); - Application.Top.Dispose (); + Application.Current.Dispose (); Application.ResetState (true); } @@ -427,19 +427,24 @@ public class ShortcutTests [InlineData (false, KeyCode.F1, 0)] public void KeyDown_App_Scope_Invokes_Action (bool canFocus, KeyCode key, int expectedAction) { - Application.Top = new (); + Application.Current = new (); var shortcut = new Shortcut { - Key = Key.A, BindKeyToApplication = true, + Key = Key.A, Text = "0", Title = "_C", CanFocus = canFocus }; - Application.Top.Add (shortcut); - Application.Top.SetFocus (); + Application.Current.Add (shortcut); + + // Shortcut requires Init for App scoped hotkeys to work + Application.Current.BeginInit (); + Application.Current.EndInit (); + + Application.Current.SetFocus (); var action = 0; shortcut.Action += () => action++; @@ -448,7 +453,7 @@ public class ShortcutTests Assert.Equal (expectedAction, action); - Application.Top.Dispose (); + Application.Current.Dispose (); Application.ResetState (true); } @@ -456,11 +461,11 @@ public class ShortcutTests [Fact] public void Scheme_SetScheme_Does_Not_Fault_3664 () { - Application.Top = new (); - Application.Navigation = new (); + Application.Current = new (); + var shortcut = new Shortcut (); - Application.Top.SetScheme (null); + Application.Current.SetScheme (null); Assert.False (shortcut.HasScheme); Assert.NotNull (shortcut.GetScheme ()); @@ -470,7 +475,7 @@ public class ShortcutTests Assert.False (shortcut.HasScheme); Assert.NotNull (shortcut.GetScheme ()); - Application.Top.Dispose (); + Application.Current.Dispose (); Application.ResetState (); } } diff --git a/Tests/UnitTests/Views/SpinnerViewTests.cs b/Tests/UnitTests/Views/SpinnerViewTests.cs index f68b96b0a..35b25e039 100644 --- a/Tests/UnitTests/Views/SpinnerViewTests.cs +++ b/Tests/UnitTests/Views/SpinnerViewTests.cs @@ -40,7 +40,7 @@ public class SpinnerViewTests (ITestOutputHelper output) // Dispose clears timeout view.Dispose (); Assert.Empty (Application.TimedEvents.Timeouts); - Application.Top.Dispose (); + Application.Current.Dispose (); } [Fact] @@ -57,12 +57,12 @@ public class SpinnerViewTests (ITestOutputHelper output) DriverAssert.AssertDriverContentsWithFrameAre (expected, output); view.AdvanceAnimation (); - View.SetClipToScreen (); + view.SetClipToScreen (); view.Draw (); expected = "/"; DriverAssert.AssertDriverContentsWithFrameAre (expected, output); - Application.Top.Dispose (); + Application.Current.Dispose (); } [Fact] @@ -95,7 +95,7 @@ public class SpinnerViewTests (ITestOutputHelper output) //expected = "|"; //DriverAsserts.AssertDriverContentsWithFrameAre (expected, output); - Application.Top.Dispose (); + Application.Current.Dispose (); } private SpinnerView GetSpinnerView () diff --git a/Tests/UnitTests/Views/TabViewTests.cs b/Tests/UnitTests/Views/TabViewTests.cs index 746f0307d..db11ed0eb 100644 --- a/Tests/UnitTests/Views/TabViewTests.cs +++ b/Tests/UnitTests/Views/TabViewTests.cs @@ -664,7 +664,7 @@ public class TabViewTests (ITestOutputHelper output) Assert.Equal (tab2, tv.SubViews.First (v => v.Id.Contains ("tabRow")).MostFocused); tv.Layout (); - View.SetClipToScreen (); + tv.SetClipToScreen (); tv.Draw (); DriverAssert.AssertDriverContentsWithFrameAre ( @@ -683,7 +683,7 @@ public class TabViewTests (ITestOutputHelper output) tab1.DisplayText = "12345678910"; tab2.DisplayText = "13"; tv.Layout (); - View.SetClipToScreen (); + tv.SetClipToScreen (); tv.Draw (); DriverAssert.AssertDriverContentsWithFrameAre ( @@ -700,7 +700,7 @@ public class TabViewTests (ITestOutputHelper output) tv.SelectedTab = tab2; tv.Layout (); - View.SetClipToScreen (); + tv.SetClipToScreen (); tv.Draw (); DriverAssert.AssertDriverContentsWithFrameAre ( @" @@ -717,7 +717,7 @@ public class TabViewTests (ITestOutputHelper output) tab2.DisplayText = "abcdefghijklmnopq"; tv.Layout (); - View.SetClipToScreen (); + tv.SetClipToScreen (); tv.Draw (); DriverAssert.AssertDriverContentsWithFrameAre ( @" @@ -810,7 +810,7 @@ public class TabViewTests (ITestOutputHelper output) Assert.Equal (tab2, tv.SubViews.First (v => v.Id.Contains ("tabRow")).MostFocused); tv.Layout (); - View.SetClipToScreen (); + tv.SetClipToScreen (); tv.Draw (); DriverAssert.AssertDriverContentsWithFrameAre ( @@ -830,7 +830,7 @@ public class TabViewTests (ITestOutputHelper output) tab2.DisplayText = "13"; tv.Layout (); - View.SetClipToScreen (); + tv.SetClipToScreen (); tv.Draw (); DriverAssert.AssertDriverContentsWithFrameAre ( @@ -847,7 +847,7 @@ public class TabViewTests (ITestOutputHelper output) tv.SelectedTab = tab2; tv.Layout (); - View.SetClipToScreen (); + tv.SetClipToScreen (); tv.Draw (); DriverAssert.AssertDriverContentsWithFrameAre ( @@ -865,7 +865,7 @@ public class TabViewTests (ITestOutputHelper output) tab2.DisplayText = "abcdefghijklmnopq"; tv.Layout (); - View.SetClipToScreen (); + tv.SetClipToScreen (); tv.Draw (); DriverAssert.AssertDriverContentsWithFrameAre ( @@ -910,7 +910,7 @@ public class TabViewTests (ITestOutputHelper output) tv.Height = 5; tv.Layout (); - View.SetClipToScreen (); + tv.SetClipToScreen (); tv.Draw (); DriverAssert.AssertDriverContentsWithFrameAre ( @@ -952,7 +952,7 @@ public class TabViewTests (ITestOutputHelper output) tv.SelectedTab = tab2; tv.Layout (); - View.SetClipToScreen (); + tv.SetClipToScreen (); tv.Draw (); DriverAssert.AssertDriverContentsWithFrameAre ( @@ -972,7 +972,7 @@ public class TabViewTests (ITestOutputHelper output) tab2.DisplayText = "13"; tv.Layout (); - View.SetClipToScreen (); + tv.SetClipToScreen (); tv.Draw (); DriverAssert.AssertDriverContentsWithFrameAre ( @@ -989,7 +989,7 @@ public class TabViewTests (ITestOutputHelper output) tv.SelectedTab = tab2; tv.Layout (); - View.SetClipToScreen (); + tv.SetClipToScreen (); tv.Draw (); DriverAssert.AssertDriverContentsWithFrameAre ( @@ -1007,7 +1007,7 @@ public class TabViewTests (ITestOutputHelper output) tab2.DisplayText = "abcdefghijklmnopq"; tv.Layout (); - View.SetClipToScreen (); + tv.SetClipToScreen (); tv.Draw (); DriverAssert.AssertDriverContentsWithFrameAre ( @@ -1049,7 +1049,7 @@ public class TabViewTests (ITestOutputHelper output) tv.SelectedTab = tab2; tv.Layout (); - View.SetClipToScreen (); + tv.SetClipToScreen (); tv.Draw (); DriverAssert.AssertDriverContentsWithFrameAre ( @@ -1144,7 +1144,7 @@ public class TabViewTests (ITestOutputHelper output) tab2.DisplayText = "13"; tv.Layout (); - View.SetClipToScreen (); + tv.SetClipToScreen (); tv.Draw (); DriverAssert.AssertDriverContentsWithFrameAre ( @@ -1161,7 +1161,7 @@ public class TabViewTests (ITestOutputHelper output) tv.SelectedTab = tab2; tv.Layout (); - View.SetClipToScreen (); + tv.SetClipToScreen (); tv.Draw (); DriverAssert.AssertDriverContentsWithFrameAre ( @@ -1179,7 +1179,7 @@ public class TabViewTests (ITestOutputHelper output) tab2.DisplayText = "abcdefghijklmnopq"; tv.Layout (); - View.SetClipToScreen (); + tv.SetClipToScreen (); tv.Draw (); DriverAssert.AssertDriverContentsWithFrameAre ( @@ -1223,7 +1223,7 @@ public class TabViewTests (ITestOutputHelper output) tv.SelectedTab = tab2; tv.Layout (); - View.SetClipToScreen (); + tv.SetClipToScreen (); tv.Draw (); DriverAssert.AssertDriverContentsWithFrameAre ( @@ -1337,7 +1337,7 @@ public class TabViewTests (ITestOutputHelper output) tv.SelectedTab = tab2; tv.Layout (); - View.SetClipToScreen (); + tv.SetClipToScreen (); tv.Draw (); DriverAssert.AssertDriverContentsWithFrameAre ( @@ -1354,7 +1354,7 @@ public class TabViewTests (ITestOutputHelper output) tv.SelectedTab = tab3; tv.Layout (); - View.SetClipToScreen (); + tv.SetClipToScreen (); tv.Draw (); DriverAssert.AssertDriverContentsWithFrameAre ( @@ -1404,7 +1404,7 @@ public class TabViewTests (ITestOutputHelper output) tv.SelectedTab = tab2; tv.Layout (); - View.SetClipToScreen (); + tv.SetClipToScreen (); tv.Draw (); DriverAssert.AssertDriverContentsWithFrameAre ( @@ -1421,7 +1421,7 @@ public class TabViewTests (ITestOutputHelper output) tv.SelectedTab = tab3; tv.Layout (); - View.SetClipToScreen (); + tv.SetClipToScreen (); tv.Draw (); DriverAssert.AssertDriverContentsWithFrameAre ( @@ -1489,7 +1489,11 @@ public class TabViewTests (ITestOutputHelper output) private TabView GetTabView (out Tab tab1, out Tab tab2) { - var tv = new TabView () { Id = "tv " }; + var tv = new TabView () + { + Driver = ApplicationImpl.Instance.Driver, + Id = "tv " + }; tv.BeginInit (); tv.EndInit (); //tv.Scheme = new (); diff --git a/Tests/UnitTests/Views/TableViewTests.cs b/Tests/UnitTests/Views/TableViewTests.cs index 885bb435a..4c162a037 100644 --- a/Tests/UnitTests/Views/TableViewTests.cs +++ b/Tests/UnitTests/Views/TableViewTests.cs @@ -57,7 +57,11 @@ public class TableViewTests (ITestOutputHelper output) [AutoInitShutdown] public void CellEventsBackgroundFill () { - var tv = new TableView { Width = 20, Height = 4 }; + var tv = new TableView + { + Driver = ApplicationImpl.Instance.Driver, + Width = 20, Height = 4 + }; var dt = new DataTable (); dt.Columns.Add ("C1"); @@ -677,7 +681,10 @@ public class TableViewTests (ITestOutputHelper output) [SetupFakeApplication] public void ScrollIndicators () { - var tableView = new TableView (); + var tableView = new TableView () + { + Driver = ApplicationImpl.Instance.Driver + }; tableView.BeginInit (); tableView.EndInit (); @@ -722,7 +729,7 @@ public class TableViewTests (ITestOutputHelper output) // since A is now pushed off screen we get indicator showing // that user can scroll left to see first column - View.SetClipToScreen (); + tableView.SetClipToScreen (); tableView.Draw (); expected = @@ -737,7 +744,7 @@ public class TableViewTests (ITestOutputHelper output) tableView.NewKeyDownEvent (new () { KeyCode = KeyCode.CursorRight }); tableView.NewKeyDownEvent (new () { KeyCode = KeyCode.CursorRight }); - View.SetClipToScreen (); + tableView.SetClipToScreen (); tableView.Draw (); expected = @@ -756,7 +763,10 @@ public class TableViewTests (ITestOutputHelper output) [SetupFakeApplication] public void ScrollRight_SmoothScrolling () { - var tableView = new TableView (); + var tableView = new TableView () + { + Driver = ApplicationImpl.Instance.Driver + }; tableView.BeginInit (); tableView.EndInit (); @@ -796,7 +806,7 @@ public class TableViewTests (ITestOutputHelper output) // Scroll right tableView.NewKeyDownEvent (new () { KeyCode = KeyCode.CursorRight }); - View.SetClipToScreen (); + tableView.SetClipToScreen (); tableView.Draw (); // Note that with SmoothHorizontalScrolling only a single new column @@ -818,7 +828,11 @@ public class TableViewTests (ITestOutputHelper output) [SetupFakeApplication] public void ScrollRight_WithoutSmoothScrolling () { - var tableView = new TableView (); + var tableView = new TableView () + { + Driver = ApplicationImpl.Instance.Driver + }; + tableView.BeginInit (); tableView.EndInit (); tableView.SchemeName = "TopLevel"; @@ -844,7 +858,7 @@ public class TableViewTests (ITestOutputHelper output) // select last visible column tableView.SelectedColumn = 2; // column C - View.SetClipToScreen (); + tableView.SetClipToScreen (); tableView.Draw (); var expected = @@ -856,7 +870,7 @@ public class TableViewTests (ITestOutputHelper output) // Scroll right tableView.NewKeyDownEvent (new () { KeyCode = KeyCode.CursorRight }); - View.SetClipToScreen (); + tableView.SetClipToScreen (); tableView.Draw (); // notice that without smooth scrolling we just update the first column @@ -1563,7 +1577,10 @@ public class TableViewTests (ITestOutputHelper output) [AutoInitShutdown] public void Test_CollectionNavigator () { - var tv = new TableView (); + var tv = new TableView () + { + Driver = ApplicationImpl.Instance.Driver + }; tv.SchemeName = "TopLevel"; tv.Viewport = new (0, 0, 50, 7); @@ -1974,7 +1991,7 @@ public class TableViewTests (ITestOutputHelper output) ◄─┼─┼─┤ │2│3│4│"; tableView.SetNeedsDraw (); - View.SetClipToScreen (); + tableView.SetClipToScreen (); tableView.Draw (); DriverAssert.AssertDriverContentsAre (expected, output); @@ -1988,7 +2005,7 @@ public class TableViewTests (ITestOutputHelper output) ├─┼─┼─┤ │2│3│4│"; tableView.SetNeedsDraw (); - View.SetClipToScreen (); + tableView.SetClipToScreen (); tableView.Draw (); DriverAssert.AssertDriverContentsAre (expected, output); @@ -2004,7 +2021,7 @@ public class TableViewTests (ITestOutputHelper output) tableView.Style.ShowHorizontalHeaderUnderline = true; tableView.LayoutSubViews (); tableView.SetNeedsDraw (); - View.SetClipToScreen (); + tableView.SetClipToScreen (); tableView.Draw (); // normally we should have scroll indicators because DEF are of screen @@ -2027,7 +2044,7 @@ public class TableViewTests (ITestOutputHelper output) ├─┼─┼─┤ │1│2│3│"; tableView.SetNeedsDraw (); - View.SetClipToScreen (); + tableView.SetClipToScreen (); tableView.Draw (); DriverAssert.AssertDriverContentsAre (expected, output); } @@ -2206,8 +2223,11 @@ public class TableViewTests (ITestOutputHelper output) [SetupFakeApplication] public void TestEnumerableDataSource_BasicTypes () { - Application.Driver!.SetScreenSize (100, 100); - var tv = new TableView (); + ApplicationImpl.Instance.Driver!.SetScreenSize (100, 100); + var tv = new TableView () + { + Driver = ApplicationImpl.Instance.Driver + }; tv.SchemeName = "TopLevel"; tv.Viewport = new (0, 0, 50, 6); @@ -2411,7 +2431,10 @@ A B C { IList list = BuildList (16); - var tv = new TableView (); + var tv = new TableView () + { + Driver = ApplicationImpl.Instance.Driver + }; //tv.BeginInit (); tv.EndInit (); tv.SchemeName = "TopLevel"; @@ -2600,7 +2623,7 @@ A B C Assert.True (pets.First ().IsPicked); - View.SetClipToScreen (); + tv.SetClipToScreen (); tv.Draw (); expected = @@ -2620,7 +2643,7 @@ A B C Assert.True (pets.ElementAt (0).IsPicked); Assert.True (pets.ElementAt (1).IsPicked); Assert.False (pets.ElementAt (2).IsPicked); - View.SetClipToScreen (); + tv.SetClipToScreen (); tv.Draw (); expected = @@ -2640,7 +2663,7 @@ A B C Assert.False (pets.ElementAt (0).IsPicked); Assert.True (pets.ElementAt (1).IsPicked); Assert.False (pets.ElementAt (2).IsPicked); - View.SetClipToScreen (); + tv.SetClipToScreen (); tv.Draw (); expected = @@ -2668,7 +2691,7 @@ A B C wrapper.CheckedRows.Add (0); wrapper.CheckedRows.Add (2); - View.SetClipToScreen (); + tv.SetClipToScreen (); tv.Draw (); var expected = @@ -2692,7 +2715,7 @@ A B C Assert.Contains (2, wrapper.CheckedRows); Assert.Equal (3, wrapper.CheckedRows.Count); - View.SetClipToScreen (); + tv.SetClipToScreen (); tv.Draw (); expected = @@ -2708,7 +2731,7 @@ A B C // Untoggle the top 2 tv.NewKeyDownEvent (Key.Space); - View.SetClipToScreen (); + tv.SetClipToScreen (); tv.Draw (); expected = @@ -2737,7 +2760,7 @@ A B C tv.NewKeyDownEvent (Key.A.WithCtrl); tv.NewKeyDownEvent (Key.Space); - View.SetClipToScreen (); + tv.SetClipToScreen (); tv.Draw (); var expected = @@ -2757,7 +2780,7 @@ A B C // Untoggle all again tv.NewKeyDownEvent (Key.Space); - View.SetClipToScreen (); + tv.SetClipToScreen (); tv.Draw (); expected = @@ -2798,7 +2821,7 @@ A B C Assert.True (pets.All (p => p.IsPicked)); - View.SetClipToScreen (); + tv.SetClipToScreen (); tv.Draw (); var expected = @@ -2818,7 +2841,7 @@ A B C Assert.Empty (pets.Where (p => p.IsPicked)); #pragma warning restore xUnit2029 - View.SetClipToScreen (); + tv.SetClipToScreen (); tv.Draw (); expected = @@ -2845,7 +2868,7 @@ A B C var wrapper = new CheckBoxTableSourceWrapperByIndex (tv, tv.Table); tv.Table = wrapper; - View.SetClipToScreen (); + tv.SetClipToScreen (); tv.Draw (); var expected = @@ -2865,7 +2888,7 @@ A B C Assert.Single (wrapper.CheckedRows, 0); - View.SetClipToScreen (); + tv.SetClipToScreen (); tv.Draw (); expected = @@ -2885,7 +2908,7 @@ A B C Assert.Contains (1, wrapper.CheckedRows); Assert.Equal (2, wrapper.CheckedRows.Count); - View.SetClipToScreen (); + tv.SetClipToScreen (); tv.Draw (); expected = @@ -2904,7 +2927,7 @@ A B C Assert.Single (wrapper.CheckedRows, 1); - View.SetClipToScreen (); + tv.SetClipToScreen (); tv.Draw (); expected = @@ -2936,7 +2959,7 @@ A B C wrapper.UseRadioButtons = true; tv.Table = wrapper; - View.SetClipToScreen (); + tv.SetClipToScreen (); tv.Draw (); var expected = @@ -2959,7 +2982,7 @@ A B C Assert.True (pets.First ().IsPicked); - View.SetClipToScreen (); + tv.SetClipToScreen (); tv.Draw (); expected = @@ -2980,7 +3003,7 @@ A B C Assert.True (pets.ElementAt (1).IsPicked); Assert.False (pets.ElementAt (2).IsPicked); - View.SetClipToScreen (); + tv.SetClipToScreen (); tv.Draw (); expected = @@ -3001,7 +3024,7 @@ A B C Assert.False (pets.ElementAt (1).IsPicked); Assert.False (pets.ElementAt (2).IsPicked); - View.SetClipToScreen (); + tv.SetClipToScreen (); tv.Draw (); expected = @@ -3234,19 +3257,19 @@ A B C // Pressing left should move us to the first column without changing focus Application.RaiseKeyDownEvent (Key.CursorLeft); - Assert.Same (tableView, Application.Top!.MostFocused); + Assert.Same (tableView, Application.Current!.MostFocused); Assert.True (tableView.HasFocus); // Because we are now on the leftmost cell a further left press should move focus Application.RaiseKeyDownEvent (Key.CursorLeft); - Assert.NotSame (tableView, Application.Top.MostFocused); + Assert.NotSame (tableView, Application.Current.MostFocused); Assert.False (tableView.HasFocus); - Assert.Same (tf1, Application.Top.MostFocused); + Assert.Same (tf1, Application.Current.MostFocused); Assert.True (tf1.HasFocus); - Application.Top.Dispose (); + Application.Current.Dispose (); } [Fact] @@ -3259,19 +3282,19 @@ A B C // First press should move us up Application.RaiseKeyDownEvent (Key.CursorUp); - Assert.Same (tableView, Application.Top!.MostFocused); + Assert.Same (tableView, Application.Current!.MostFocused); Assert.True (tableView.HasFocus); // Because we are now on the top row a further press should move focus Application.RaiseKeyDownEvent (Key.CursorUp); - Assert.NotSame (tableView, Application.Top.MostFocused); + Assert.NotSame (tableView, Application.Current.MostFocused); Assert.False (tableView.HasFocus); - Assert.Same (tf1, Application.Top.MostFocused); + Assert.Same (tf1, Application.Current.MostFocused); Assert.True (tf1.HasFocus); - Application.Top.Dispose (); + Application.Current.Dispose (); } [Fact] @@ -3284,19 +3307,19 @@ A B C // First press should move us to the rightmost column without changing focus Application.RaiseKeyDownEvent (Key.CursorRight); - Assert.Same (tableView, Application.Top!.MostFocused); + Assert.Same (tableView, Application.Current!.MostFocused); Assert.True (tableView.HasFocus); // Because we are now on the rightmost cell, a further right press should move focus Application.RaiseKeyDownEvent (Key.CursorRight); - Assert.NotSame (tableView, Application.Top.MostFocused); + Assert.NotSame (tableView, Application.Current.MostFocused); Assert.False (tableView.HasFocus); - Assert.Same (tf2, Application.Top.MostFocused); + Assert.Same (tf2, Application.Current.MostFocused); Assert.True (tf2.HasFocus); - Application.Top.Dispose (); + Application.Current.Dispose (); } [Fact] @@ -3309,19 +3332,19 @@ A B C // First press should move us to the bottommost row without changing focus Application.RaiseKeyDownEvent (Key.CursorDown); - Assert.Same (tableView, Application.Top!.MostFocused); + Assert.Same (tableView, Application.Current!.MostFocused); Assert.True (tableView.HasFocus); // Because we are now on the bottommost cell, a further down press should move focus Application.RaiseKeyDownEvent (Key.CursorDown); - Assert.NotSame (tableView, Application.Top.MostFocused); + Assert.NotSame (tableView, Application.Current.MostFocused); Assert.False (tableView.HasFocus); - Assert.Same (tf2, Application.Top.MostFocused); + Assert.Same (tf2, Application.Current.MostFocused); Assert.True (tf2.HasFocus); - Application.Top.Dispose (); + Application.Current.Dispose (); } [Fact] @@ -3334,7 +3357,7 @@ A B C // Pressing shift-left should give us a multi selection Application.RaiseKeyDownEvent (Key.CursorLeft.WithShift); - Assert.Same (tableView, Application.Top!.MostFocused); + Assert.Same (tableView, Application.Current!.MostFocused); Assert.True (tableView.HasFocus); Assert.Equal (2, tableView.GetAllSelectedCells ().Count ()); @@ -3345,19 +3368,19 @@ A B C // Selection 'clears' just to the single cell and we remain focused Assert.Single (tableView.GetAllSelectedCells ()); - Assert.Same (tableView, Application.Top.MostFocused); + Assert.Same (tableView, Application.Current.MostFocused); Assert.True (tableView.HasFocus); // A further left will switch focus Application.RaiseKeyDownEvent (Key.CursorLeft); - Assert.NotSame (tableView, Application.Top.MostFocused); + Assert.NotSame (tableView, Application.Current.MostFocused); Assert.False (tableView.HasFocus); - Assert.Same (tf1, Application.Top.MostFocused); + Assert.Same (tf1, Application.Current.MostFocused); Assert.True (tf1.HasFocus); - Application.Top.Dispose (); + Application.Current.Dispose (); } [Theory] @@ -3397,17 +3420,17 @@ A B C tableView.BeginInit (); tableView.EndInit (); - Application.Navigation = new (); - Application.Top = new (); + + Application.Current = new (); tf1 = new (); tf2 = new (); - Application.Top.Add (tf1); - Application.Top.Add (tableView); - Application.Top.Add (tf2); + Application.Current.Add (tf1); + Application.Current.Add (tableView); + Application.Current.Add (tf2); tableView.SetFocus (); - Assert.Same (tableView, Application.Top.MostFocused); + Assert.Same (tableView, Application.Current.MostFocused); Assert.True (tableView.HasFocus); // Set big table @@ -3416,7 +3439,10 @@ A B C private TableView GetABCDEFTableView (out DataTable dt) { - var tableView = new TableView (); + var tableView = new TableView () + { + Driver = ApplicationImpl.Instance.Driver + }; tableView.BeginInit (); tableView.EndInit (); @@ -3445,9 +3471,12 @@ A B C private TableView GetPetTable (out EnumerableTableSource source) { - var tv = new TableView (); - tv.SchemeName = "TopLevel"; - tv.Viewport = new (0, 0, 25, 6); + var tv = new TableView () + { + Driver = ApplicationImpl.Instance.Driver, + SchemeName = "TopLevel", + Viewport = new (0, 0, 25, 6) + }; List pets = new () { @@ -3473,7 +3502,10 @@ A B C private TableView GetTwoRowSixColumnTable (out DataTable dt) { - var tableView = new TableView (); + var tableView = new TableView () + { + Driver = ApplicationImpl.Instance.Driver + }; tableView.SchemeName = "TopLevel"; // 3 columns are visible @@ -3503,7 +3535,10 @@ A B C private TableView SetUpMiniTable (out DataTable dt) { - var tv = new TableView (); + var tv = new TableView () + { + Driver = ApplicationImpl.Instance.Driver + }; tv.BeginInit (); tv.EndInit (); tv.Viewport = new (0, 0, 10, 4); diff --git a/Tests/UnitTests/Views/TextFieldTests.cs b/Tests/UnitTests/Views/TextFieldTests.cs index b4d27b93b..922178409 100644 --- a/Tests/UnitTests/Views/TextFieldTests.cs +++ b/Tests/UnitTests/Views/TextFieldTests.cs @@ -84,7 +84,7 @@ public class TextFieldTests (ITestOutputHelper output) tf.Draw (); DriverAssert.AssertDriverContentsAre (expectedRender, output); - Application.Top.Dispose (); + Application.Current.Dispose (); } [Fact] @@ -104,7 +104,7 @@ public class TextFieldTests (ITestOutputHelper output) tf.Draw (); DriverAssert.AssertDriverContentsAre ("Misérables", output); - Application.Top.Dispose (); + Application.Current.Dispose (); } [Theory (Skip = "Broke with ContextMenuv2")] @@ -123,16 +123,16 @@ public class TextFieldTests (ITestOutputHelper output) // Caption should appear when not focused and no text Assert.False (tf.HasFocus); - View.SetClipToScreen (); + tf.SetClipToScreen (); tf.Draw (); DriverAssert.AssertDriverContentsAre ("Enter txt", output); // but disapear when text is added tf.Text = content; - View.SetClipToScreen (); + tf.SetClipToScreen (); tf.Draw (); DriverAssert.AssertDriverContentsAre (content, output); - Application.Top.Dispose (); + Application.Current.Dispose (); } [Fact] @@ -147,17 +147,17 @@ public class TextFieldTests (ITestOutputHelper output) // Caption has no effect when focused tf.Title = "Enter txt"; Assert.True (tf.HasFocus); - View.SetClipToScreen (); + tf.SetClipToScreen (); tf.Draw (); DriverAssert.AssertDriverContentsAre ("", output); Application.RaiseKeyDownEvent ('\t'); Assert.False (tf.HasFocus); - View.SetClipToScreen (); + tf.SetClipToScreen (); tf.Draw (); DriverAssert.AssertDriverContentsAre ("Enter txt", output); - Application.Top.Dispose (); + Application.Current.Dispose (); } [Fact] @@ -173,7 +173,7 @@ public class TextFieldTests (ITestOutputHelper output) Application.RaiseKeyDownEvent ('\t'); Assert.False (tf.HasFocus); - View.SetClipToScreen (); + tf.SetClipToScreen (); tf.Draw (); // Verify the caption text is rendered @@ -187,7 +187,7 @@ public class TextFieldTests (ITestOutputHelper output) // All characters in "Enter text" should have the caption attribute DriverAssert.AssertDriverAttributesAre ("0000000000", output, Application.Driver, captionAttr); - Application.Top.Dispose (); + Application.Current.Dispose (); } [Fact] @@ -203,7 +203,7 @@ public class TextFieldTests (ITestOutputHelper output) Application.RaiseKeyDownEvent ('\t'); Assert.False (tf.HasFocus); - View.SetClipToScreen (); + tf.SetClipToScreen (); tf.Draw (); // The hotkey character 'F' should be rendered (without the underscore in the actual text) @@ -221,7 +221,7 @@ public class TextFieldTests (ITestOutputHelper output) // F is underlined (index 1), remaining characters use normal caption attribute (index 0) DriverAssert.AssertDriverAttributesAre ("1000", output, Application.Driver, captionAttr, hotkeyAttr); - Application.Top.Dispose (); + Application.Current.Dispose (); } [Fact] @@ -237,7 +237,7 @@ public class TextFieldTests (ITestOutputHelper output) Application.RaiseKeyDownEvent ('\t'); Assert.False (tf.HasFocus); - View.SetClipToScreen (); + tf.SetClipToScreen (); tf.Draw (); // The underscore should not be rendered, 'T' should be underlined @@ -255,7 +255,7 @@ public class TextFieldTests (ITestOutputHelper output) // "Enter " (6 chars) + "T" (underlined) + "ext" (3 chars) DriverAssert.AssertDriverAttributesAre ("0000001000", output, Application.Driver, captionAttr, hotkeyAttr); - Application.Top.Dispose (); + Application.Current.Dispose (); } [Fact] @@ -1620,7 +1620,11 @@ public class TextFieldTests (ITestOutputHelper output) [SetupFakeApplication] public void Words_With_Accents_Incorrect_Order_Will_Result_With_Wrong_Accent_Place () { - var tf = new TextField { Width = 30, Text = "Les Misérables" }; + var tf = new TextField + { + Driver = ApplicationImpl.Instance.Driver, + Width = 30, Text = "Les Misérables" + }; tf.SetRelativeLayout (new (100, 100)); tf.Draw (); @@ -1641,7 +1645,7 @@ Les Misérables", // incorrect order will result with a wrong accent place tf.Text = "Les Mis" + char.ConvertFromUtf32 (int.Parse ("0301", NumberStyles.HexNumber)) + "erables"; - View.SetClipToScreen (); + tf.SetClipToScreen (); tf.Draw (); DriverAssert.AssertDriverContentsWithFrameAre ( @@ -1686,7 +1690,7 @@ Les Miśerables", { base.Before (methodUnderTest); - //Application.Top.Scheme = Colors.Schemes ["Base"]; + //Application.Current.Scheme = Colors.Schemes ["Base"]; _textField = new () { // 1 2 3 @@ -1701,7 +1705,11 @@ Les Miśerables", [AutoInitShutdown] public void Draw_Esc_Rune () { - var tf = new TextField { Width = 5, Text = "\u001b" }; + var tf = new TextField + { + Driver = ApplicationImpl.Instance.Driver, + Width = 5, Text = "\u001b" + }; tf.BeginInit (); tf.EndInit (); tf.Draw (); diff --git a/Tests/UnitTests/Views/TextViewTests.cs b/Tests/UnitTests/Views/TextViewTests.cs index d00000d2e..5d03983eb 100644 --- a/Tests/UnitTests/Views/TextViewTests.cs +++ b/Tests/UnitTests/Views/TextViewTests.cs @@ -109,7 +109,7 @@ public class TextViewTests Assert.Equal (leftCol, _textView.LeftColumn); } - Application.Top.Remove (_textView); + Application.Current.Remove (_textView); Application.RequestStop (); } } @@ -6805,7 +6805,10 @@ Line 2.", ); tv.WordWrap = true; - var top = new Toplevel (); + var top = new Toplevel () + { + Driver = ApplicationImpl.Instance.Driver, + }; top.Add (tv); top.Layout (); tv.Draw (); @@ -6827,7 +6830,7 @@ line. tv.CursorPosition = new (6, 2); Assert.Equal (new (5, 2), tv.CursorPosition); top.LayoutSubViews (); - View.SetClipToScreen (); + top.SetClipToScreen (); tv.Draw (); DriverAssert.AssertDriverContentsWithFrameAre ( @@ -6891,7 +6894,10 @@ line. ); tv.WordWrap = true; - var top = new Toplevel (); + var top = new Toplevel () + { + Driver = ApplicationImpl.Instance.Driver, + }; top.Add (tv); top.Layout (); @@ -7005,7 +7011,11 @@ line. [SetupFakeApplication] public void Draw_Esc_Rune () { - var tv = new TextView { Width = 5, Height = 1, Text = "\u001b" }; + var tv = new TextView + { + Driver = ApplicationImpl.Instance.Driver, + Width = 5, Height = 1, Text = "\u001b" + }; tv.BeginInit (); tv.EndInit (); tv.Draw (); diff --git a/Tests/UnitTests/Views/ToplevelTests.cs b/Tests/UnitTests/Views/ToplevelTests.cs index ad47948a0..365ebd8bf 100644 --- a/Tests/UnitTests/Views/ToplevelTests.cs +++ b/Tests/UnitTests/Views/ToplevelTests.cs @@ -69,11 +69,11 @@ public class ToplevelTests #endif Application.Begin (top); - Assert.Equal (top, Application.Top); + Assert.Equal (top, Application.Current); - // Application.Top without menu and status bar. + // Application.Current without menu and status bar. View supView = View.GetLocationEnsuringFullVisibility (top, 2, 2, out int nx, out int ny /*, out StatusBar sb*/); - Assert.Equal (Application.Top, supView); + Assert.Equal (Application.Current, supView); Assert.Equal (0, nx); Assert.Equal (0, ny); @@ -82,7 +82,7 @@ public class ToplevelTests top.Add (new MenuBar ()); Assert.NotNull (top.MenuBar); - // Application.Top with a menu and without status bar. + // Application.Current with a menu and without status bar. View.GetLocationEnsuringFullVisibility (top, 2, 2, out nx, out ny /*, out sb*/); Assert.Equal (0, nx); Assert.Equal (1, ny); @@ -92,11 +92,11 @@ public class ToplevelTests //top.Add (new StatusBar ()); //Assert.NotNull (top.StatusBar); - // Application.Top with a menu and status bar. + // Application.Current with a menu and status bar. View.GetLocationEnsuringFullVisibility (top, 2, 2, out nx, out ny /*, out sb*/); Assert.Equal (0, nx); - // The available height is lower than the Application.Top height minus + // The available height is lower than the Application.Current height minus // the menu bar and status bar, then the top can go beyond the bottom // Assert.Equal (2, ny); //Assert.NotNull (sb); @@ -106,11 +106,11 @@ public class ToplevelTests Assert.Null (top.MenuBar); Assert.NotNull (menuBar); - // Application.Top without a menu and with a status bar. + // Application.Current without a menu and with a status bar. View.GetLocationEnsuringFullVisibility (top, 2, 2, out nx, out ny /*, out sb*/); Assert.Equal (0, nx); - // The available height is lower than the Application.Top height minus + // The available height is lower than the Application.Current height minus // the status bar, then the top can go beyond the bottom // Assert.Equal (2, ny); //Assert.NotNull (sb); @@ -127,11 +127,11 @@ public class ToplevelTests // The SuperView is always the same regardless of the caller. supView = View.GetLocationEnsuringFullVisibility (win, 0, 0, out nx, out ny /*, out sb*/); - Assert.Equal (Application.Top, supView); + Assert.Equal (Application.Current, supView); supView = View.GetLocationEnsuringFullVisibility (win, 0, 0, out nx, out ny /*, out sb*/); - Assert.Equal (Application.Top, supView); + Assert.Equal (Application.Current, supView); - // Application.Top without menu and status bar. + // Application.Current without menu and status bar. View.GetLocationEnsuringFullVisibility (win, 0, 0, out nx, out ny /*, out sb*/); Assert.Equal (0, nx); Assert.Equal (0, ny); @@ -141,7 +141,7 @@ public class ToplevelTests top.Add (new MenuBar ()); Assert.NotNull (top.MenuBar); - // Application.Top with a menu and without status bar. + // Application.Current with a menu and without status bar. View.GetLocationEnsuringFullVisibility (win, 2, 2, out nx, out ny /*, out sb*/); Assert.Equal (0, nx); Assert.Equal (1, ny); @@ -152,11 +152,11 @@ public class ToplevelTests //Assert.NotNull (top.StatusBar); - // Application.Top with a menu and status bar. + // Application.Current with a menu and status bar. View.GetLocationEnsuringFullVisibility (win, 30, 20, out nx, out ny /*, out sb*/); Assert.Equal (0, nx); - // The available height is lower than the Application.Top height minus + // The available height is lower than the Application.Current height minus // the menu bar and status bar, then the top can go beyond the bottom //Assert.Equal (20, ny); //Assert.NotNull (sb); @@ -177,7 +177,7 @@ public class ToplevelTests win = new () { Width = 60, Height = 15 }; top.Add (win); - // Application.Top without menu and status bar. + // Application.Current without menu and status bar. View.GetLocationEnsuringFullVisibility (win, 0, 0, out nx, out ny /*, out sb*/); Assert.Equal (0, nx); Assert.Equal (0, ny); @@ -187,7 +187,7 @@ public class ToplevelTests top.Add (new MenuBar ()); Assert.NotNull (top.MenuBar); - // Application.Top with a menu and without status bar. + // Application.Current with a menu and without status bar. View.GetLocationEnsuringFullVisibility (win, 2, 2, out nx, out ny /*, out sb*/); Assert.Equal (2, nx); Assert.Equal (2, ny); @@ -198,7 +198,7 @@ public class ToplevelTests //Assert.NotNull (top.StatusBar); - // Application.Top with a menu and status bar. + // Application.Current with a menu and status bar. View.GetLocationEnsuringFullVisibility (win, 30, 20, out nx, out ny /*, out sb*/); Assert.Equal (20, nx); // 20+60=80 @@ -249,7 +249,7 @@ public class ToplevelTests var win = new Window (); win.Add (view); - Application.Init (null, "fake"); + Application.Init ("fake"); Toplevel top = new (); top.Add (win); @@ -307,7 +307,7 @@ public class ToplevelTests } else if (iterations == 1) { - Assert.Equal (new (2, 2), Application.Top!.Frame.Location); + Assert.Equal (new (2, 2), Application.Current!.Frame.Location); } else if (iterations == 2) { @@ -316,12 +316,12 @@ public class ToplevelTests // Grab the mouse Application.RaiseMouseEvent (new () { ScreenPosition = new (3, 2), Flags = MouseFlags.Button1Pressed }); - Assert.Equal (Application.Top!.Border, Application.Mouse.MouseGrabView); - Assert.Equal (new (2, 2, 10, 3), Application.Top.Frame); + Assert.Equal (Application.Current!.Border, Application.Mouse.MouseGrabView); + Assert.Equal (new (2, 2, 10, 3), Application.Current.Frame); } else if (iterations == 3) { - Assert.Equal (Application.Top!.Border, Application.Mouse.MouseGrabView); + Assert.Equal (Application.Current!.Border, Application.Mouse.MouseGrabView); // Drag to left Application.RaiseMouseEvent ( @@ -333,38 +333,38 @@ public class ToplevelTests }); AutoInitShutdownAttribute.RunIteration (); - Assert.Equal (Application.Top.Border, Application.Mouse.MouseGrabView); - Assert.Equal (new (1, 2, 10, 3), Application.Top.Frame); + Assert.Equal (Application.Current.Border, Application.Mouse.MouseGrabView); + Assert.Equal (new (1, 2, 10, 3), Application.Current.Frame); } else if (iterations == 4) { - Assert.Equal (Application.Top!.Border, Application.Mouse.MouseGrabView); - Assert.Equal (new (1, 2), Application.Top.Frame.Location); + Assert.Equal (Application.Current!.Border, Application.Mouse.MouseGrabView); + Assert.Equal (new (1, 2), Application.Current.Frame.Location); - Assert.Equal (Application.Top.Border, Application.Mouse.MouseGrabView); + Assert.Equal (Application.Current.Border, Application.Mouse.MouseGrabView); } else if (iterations == 5) { - Assert.Equal (Application.Top!.Border, Application.Mouse.MouseGrabView); + Assert.Equal (Application.Current!.Border, Application.Mouse.MouseGrabView); // Drag up Application.RaiseMouseEvent (new () { ScreenPosition = new (2, 1), Flags = MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition }); AutoInitShutdownAttribute.RunIteration (); - Assert.Equal (Application.Top!.Border, Application.Mouse.MouseGrabView); - Assert.Equal (new (1, 1, 10, 3), Application.Top.Frame); + Assert.Equal (Application.Current!.Border, Application.Mouse.MouseGrabView); + Assert.Equal (new (1, 1, 10, 3), Application.Current.Frame); } else if (iterations == 6) { - Assert.Equal (Application.Top!.Border, Application.Mouse.MouseGrabView); - Assert.Equal (new (1, 1), Application.Top.Frame.Location); + Assert.Equal (Application.Current!.Border, Application.Mouse.MouseGrabView); + Assert.Equal (new (1, 1), Application.Current.Frame.Location); - Assert.Equal (Application.Top.Border, Application.Mouse.MouseGrabView); - Assert.Equal (new (1, 1, 10, 3), Application.Top.Frame); + Assert.Equal (Application.Current.Border, Application.Mouse.MouseGrabView); + Assert.Equal (new (1, 1, 10, 3), Application.Current.Frame); } else if (iterations == 7) { - Assert.Equal (Application.Top!.Border, Application.Mouse.MouseGrabView); + Assert.Equal (Application.Current!.Border, Application.Mouse.MouseGrabView); // Ungrab the mouse Application.RaiseMouseEvent (new () { ScreenPosition = new (2, 1), Flags = MouseFlags.Button1Released }); @@ -739,7 +739,7 @@ public class ToplevelTests [Fact] public void Multi_Thread_Toplevels () { - Application.Init (null, "fake"); + Application.Init ("fake"); Toplevel t = new (); var w = new Window (); diff --git a/Tests/UnitTests/Views/TreeTableSourceTests.cs b/Tests/UnitTests/Views/TreeTableSourceTests.cs index 43d5a2059..0f5fd42c2 100644 --- a/Tests/UnitTests/Views/TreeTableSourceTests.cs +++ b/Tests/UnitTests/Views/TreeTableSourceTests.cs @@ -55,7 +55,7 @@ public class TreeTableSourceTests : IDisposable // when pressing right we should expand the top route tv.NewKeyDownEvent (Key.CursorRight); - View.SetClipToScreen (); + tv.SetClipToScreen (); tv.Draw (); expected = @@ -73,7 +73,7 @@ public class TreeTableSourceTests : IDisposable // when pressing left we should collapse the top route again tv.NewKeyDownEvent (Key.CursorLeft); - View.SetClipToScreen (); + tv.SetClipToScreen (); tv.Draw (); expected = @@ -97,7 +97,7 @@ public class TreeTableSourceTests : IDisposable tv.Style.GetOrCreateColumnStyle (1).MinAcceptableWidth = 1; - View.SetClipToScreen (); + tv.SetClipToScreen (); tv.Draw (); var expected = @@ -117,7 +117,7 @@ public class TreeTableSourceTests : IDisposable Assert.True (tv.NewMouseEvent (new MouseEventArgs { Position = new (2, 2), Flags = MouseFlags.Button1Clicked })); - View.SetClipToScreen (); + tv.SetClipToScreen (); tv.Draw (); expected = @@ -142,7 +142,7 @@ public class TreeTableSourceTests : IDisposable // Clicking on the + again should collapse tv.NewMouseEvent (new MouseEventArgs { Position = new (2, 2), Flags = MouseFlags.Button1Clicked }); - View.SetClipToScreen (); + tv.SetClipToScreen (); tv.Draw (); expected = @@ -195,7 +195,7 @@ public class TreeTableSourceTests : IDisposable Application.RaiseKeyDownEvent (Key.CursorRight); - View.SetClipToScreen (); + tv.SetClipToScreen (); tv.Draw (); expected = @@ -213,7 +213,7 @@ public class TreeTableSourceTests : IDisposable tv.NewKeyDownEvent (Key.CursorDown); tv.NewKeyDownEvent (Key.Space); - View.SetClipToScreen (); + tv.SetClipToScreen (); tv.Draw (); expected = @@ -239,7 +239,10 @@ public class TreeTableSourceTests : IDisposable private TableView GetTreeTable (out TreeView tree) { - var tableView = new TableView (); + var tableView = new TableView () + { + Driver = ApplicationImpl.Instance.Driver, + }; tableView.SchemeName = "TopLevel"; tableView.Viewport = new Rectangle (0, 0, 40, 6); diff --git a/Tests/UnitTests/Views/TreeViewTests.cs b/Tests/UnitTests/Views/TreeViewTests.cs index 88b9cde4b..f57330054 100644 --- a/Tests/UnitTests/Views/TreeViewTests.cs +++ b/Tests/UnitTests/Views/TreeViewTests.cs @@ -93,7 +93,7 @@ public class TreeViewTests (ITestOutputHelper output) [AutoInitShutdown] public void CursorVisibility_MultiSelect () { - var tv = new TreeView { Width = 20, Height = 10 }; + var tv = new TreeView () { Driver = ApplicationImpl.Instance.Driver, Width = 20, Height = 10 }; var n1 = new TreeNode ("normal"); var n2 = new TreeNode ("pink"); @@ -698,7 +698,11 @@ public class TreeViewTests (ITestOutputHelper output) [SetupFakeApplication] public void TestBottomlessTreeView_MaxDepth_3 () { - TreeView tv = new () { Width = 20, Height = 10 }; + TreeView tv = new () + { + Driver = ApplicationImpl.Instance.Driver, + Width = 20, Height = 10 + }; tv.TreeBuilder = new DelegateTreeBuilder ( s => new [] { (int.Parse (s) + 1).ToString () } @@ -718,7 +722,7 @@ public class TreeViewTests (ITestOutputHelper output) ); tv.MaxDepth = 3; tv.ExpandAll (); - View.SetClipToScreen (); + tv.SetClipToScreen (); tv.Draw (); // Normal drawing of the tree view @@ -737,7 +741,7 @@ public class TreeViewTests (ITestOutputHelper output) [SetupFakeApplication] public void TestBottomlessTreeView_MaxDepth_5 () { - TreeView tv = new () { Width = 20, Height = 10 }; + TreeView tv = new () { Driver = ApplicationImpl.Instance.Driver, Width = 20, Height = 10 }; tv.TreeBuilder = new DelegateTreeBuilder ( s => new [] { (int.Parse (s) + 1).ToString () } @@ -757,7 +761,7 @@ public class TreeViewTests (ITestOutputHelper output) ); tv.MaxDepth = 5; tv.ExpandAll (); - View.SetClipToScreen (); + tv.SetClipToScreen (); tv.Draw (); @@ -785,7 +789,7 @@ public class TreeViewTests (ITestOutputHelper output) Assert.True (tv.CanExpand ("5")); Assert.False (tv.IsExpanded ("5")); - View.SetClipToScreen (); + tv.SetClipToScreen (); tv.Draw (); @@ -806,7 +810,7 @@ public class TreeViewTests (ITestOutputHelper output) [SetupFakeApplication] public void TestGetObjectOnRow () { - var tv = new TreeView { Width = 20, Height = 10 }; + var tv = new TreeView () { Driver = ApplicationImpl.Instance.Driver, Width = 20, Height = 10 }; tv.BeginInit (); tv.EndInit (); var n1 = new TreeNode ("normal"); @@ -840,7 +844,7 @@ public class TreeViewTests (ITestOutputHelper output) Assert.Null (tv.GetObjectOnRow (4)); tv.Collapse (n1); - View.SetClipToScreen (); + tv.SetClipToScreen (); tv.Draw (); @@ -862,7 +866,7 @@ public class TreeViewTests (ITestOutputHelper output) [SetupFakeApplication] public void TestGetObjectRow () { - var tv = new TreeView { Width = 20, Height = 10 }; + var tv = new TreeView () { Driver = ApplicationImpl.Instance.Driver, Width = 20, Height = 10 }; var n1 = new TreeNode ("normal"); var n1_1 = new TreeNode ("pink"); @@ -877,7 +881,7 @@ public class TreeViewTests (ITestOutputHelper output) tv.SetScheme (new Scheme ()); tv.LayoutSubViews (); - View.SetClipToScreen (); + tv.SetClipToScreen (); tv.Draw (); DriverAssert.AssertDriverContentsAre ( @@ -897,7 +901,7 @@ public class TreeViewTests (ITestOutputHelper output) tv.Collapse (n1); tv.LayoutSubViews (); - View.SetClipToScreen (); + tv.SetClipToScreen (); tv.Draw (); DriverAssert.AssertDriverContentsAre ( @@ -915,7 +919,7 @@ public class TreeViewTests (ITestOutputHelper output) tv.ScrollOffsetVertical = 1; tv.LayoutSubViews (); - View.SetClipToScreen (); + tv.SetClipToScreen (); tv.Draw (); DriverAssert.AssertDriverContentsAre ( @@ -933,7 +937,7 @@ public class TreeViewTests (ITestOutputHelper output) [SetupFakeApplication] public void TestTreeView_DrawLineEvent () { - var tv = new TreeView { Width = 20, Height = 10 }; + var tv = new TreeView () { Driver = ApplicationImpl.Instance.Driver, Width = 20, Height = 10 }; List> eventArgs = new (); @@ -952,7 +956,7 @@ public class TreeViewTests (ITestOutputHelper output) tv.SetScheme (new Scheme ()); tv.LayoutSubViews (); - View.SetClipToScreen (); + tv.SetClipToScreen (); tv.Draw (); // Normal drawing of the tree view @@ -1000,7 +1004,7 @@ public class TreeViewTests (ITestOutputHelper output) [SetupFakeApplication] public void TestTreeView_DrawLineEvent_Handled () { - var tv = new TreeView { Width = 20, Height = 10 }; + var tv = new TreeView () { Driver = ApplicationImpl.Instance.Driver, Width = 20, Height = 10 }; tv.DrawLine += (s, e) => { @@ -1046,7 +1050,7 @@ FFFFFFFFFF [SetupFakeApplication] public void TestTreeView_DrawLineEvent_WithScrolling () { - var tv = new TreeView { Width = 20, Height = 10 }; + var tv = new TreeView () { Driver = ApplicationImpl.Instance.Driver, Width = 20, Height = 10 }; List> eventArgs = new (); @@ -1109,7 +1113,7 @@ oot two [SetupFakeApplication] public void TestTreeView_Filter () { - var tv = new TreeView { Width = 20, Height = 10 }; + var tv = new TreeView () { Driver = ApplicationImpl.Instance.Driver, Width = 20, Height = 10 }; var n1 = new TreeNode ("root one"); var n1_1 = new TreeNode ("leaf 1"); @@ -1141,7 +1145,7 @@ oot two // matches nothing filter.Text = "asdfjhasdf"; - View.SetClipToScreen (); + tv.SetClipToScreen (); tv.Draw (); // Normal drawing of the tree view @@ -1152,7 +1156,7 @@ oot two // Matches everything filter.Text = "root"; - View.SetClipToScreen (); + tv.SetClipToScreen (); tv.Draw (); DriverAssert.AssertDriverContentsAre ( @@ -1167,7 +1171,7 @@ oot two // Matches 2 leaf nodes filter.Text = "leaf"; - View.SetClipToScreen (); + tv.SetClipToScreen (); tv.Draw (); DriverAssert.AssertDriverContentsAre ( @@ -1181,7 +1185,7 @@ oot two // Matches 1 leaf nodes filter.Text = "leaf 1"; - View.SetClipToScreen (); + tv.SetClipToScreen (); tv.Draw (); DriverAssert.AssertDriverContentsAre ( @@ -1197,7 +1201,7 @@ oot two [SetupFakeApplication] public void TestTreeViewColor () { - var tv = new TreeView { Width = 20, Height = 10 }; + var tv = new TreeView () { Driver = ApplicationImpl.Instance.Driver, Width = 20, Height = 10 }; tv.BeginInit (); tv.EndInit (); var n1 = new TreeNode ("normal"); @@ -1253,7 +1257,7 @@ oot two // redraw now that the custom color // delegate is registered tv.SetNeedsDraw (); - View.SetClipToScreen (); + tv.SetClipToScreen (); tv.Draw (); // Same text diff --git a/Tests/UnitTestsParallelizable/Application/ApplicationPopoverTests.cs b/Tests/UnitTestsParallelizable/Application/ApplicationPopoverTests.cs index 5bb470c7e..914d676ee 100644 --- a/Tests/UnitTestsParallelizable/Application/ApplicationPopoverTests.cs +++ b/Tests/UnitTestsParallelizable/Application/ApplicationPopoverTests.cs @@ -1,5 +1,6 @@ #nullable enable using Moq; +using Terminal.Gui.App; namespace UnitTests_Parallelizable.ApplicationTests; @@ -42,6 +43,7 @@ public class ApplicationPopoverTests // Arrange var popover = new Mock ().Object; var popoverManager = new ApplicationPopover (); + popoverManager.Register (popover); // Act popoverManager.Show (popover); @@ -56,6 +58,7 @@ public class ApplicationPopoverTests // Arrange var popover = new Mock ().Object; var popoverManager = new ApplicationPopover (); + popoverManager.Register (popover); popoverManager.Show (popover); // Act @@ -72,6 +75,8 @@ public class ApplicationPopoverTests // Arrange var popover = new PopoverTestClass (); var popoverManager = new ApplicationPopover (); + popoverManager.Register (popover); + popoverManager.Show (popover); // Act @@ -88,6 +93,7 @@ public class ApplicationPopoverTests // Arrange var popover = new PopoverTestClass (); var popoverManager = new ApplicationPopover (); + popoverManager.Register (popover); popoverManager.Show (popover); // Act @@ -106,6 +112,8 @@ public class ApplicationPopoverTests var activePopover = new PopoverTestClass () { Id = "activePopover" }; var inactivePopover = new PopoverTestClass () { Id = "inactivePopover" }; ; var popoverManager = new ApplicationPopover (); + + popoverManager.Register (activePopover); popoverManager.Show (activePopover); popoverManager.Register (inactivePopover); @@ -126,6 +134,8 @@ public class ApplicationPopoverTests var activePopover = new PopoverTestClass (); var inactivePopover = new PopoverTestClass (); var popoverManager = new ApplicationPopover (); + popoverManager.Register (activePopover); + popoverManager.Show (activePopover); popoverManager.Register (inactivePopover); @@ -181,6 +191,6 @@ public class ApplicationPopoverTests } /// - public Toplevel? Toplevel { get; set; } + public Toplevel? Current { get; set; } } } diff --git a/Tests/UnitTestsParallelizable/Application/MouseInterfaceTests.cs b/Tests/UnitTestsParallelizable/Application/MouseInterfaceTests.cs index 69b64f51a..22421f90b 100644 --- a/Tests/UnitTestsParallelizable/Application/MouseInterfaceTests.cs +++ b/Tests/UnitTestsParallelizable/Application/MouseInterfaceTests.cs @@ -41,7 +41,7 @@ public class MouseInterfaceTests (ITestOutputHelper output) // Assert Assert.Equal (testPosition, mouse.LastMousePosition); - Assert.Equal (testPosition, mouse.GetLastMousePosition ()); + Assert.Equal (testPosition, mouse.LastMousePosition); } [Fact] diff --git a/Tests/UnitTestsParallelizable/Application/MouseTests.cs b/Tests/UnitTestsParallelizable/Application/MouseTests.cs index fdd3260a4..d5dba4dd4 100644 --- a/Tests/UnitTestsParallelizable/Application/MouseTests.cs +++ b/Tests/UnitTestsParallelizable/Application/MouseTests.cs @@ -32,7 +32,7 @@ public class MouseTests (ITestOutputHelper output) // Act mouse.LastMousePosition = expectedPosition; - Point? actualPosition = mouse.GetLastMousePosition (); + Point? actualPosition = mouse.LastMousePosition; // Assert Assert.Equal (expectedPosition, actualPosition); diff --git a/Tests/UnitTestsParallelizable/Application/PopoverBaseImplTests.cs b/Tests/UnitTestsParallelizable/Application/PopoverBaseImplTests.cs index 69b795479..7d2d27d50 100644 --- a/Tests/UnitTestsParallelizable/Application/PopoverBaseImplTests.cs +++ b/Tests/UnitTestsParallelizable/Application/PopoverBaseImplTests.cs @@ -27,8 +27,8 @@ public class PopoverBaseImplTests { var popover = new TestPopover (); var top = new Toplevel (); - popover.Toplevel = top; - Assert.Same (top, popover.Toplevel); + popover.Current = top; + Assert.Same (top, popover.Current); } [Fact] @@ -59,11 +59,11 @@ public class PopoverBaseImplTests } [Fact] - public void Show_DoesNotThrow_BasePopoverImpl () + public void Show_Throw_If_Not_Registered () { var popover = new TestPopover (); var popoverManager = new ApplicationPopover (); - popoverManager.Show (popover); + Assert.Throws (() => popoverManager.Show (popover)); } } diff --git a/Tests/UnitTestsParallelizable/Configuration/SourcesManagerTests.cs b/Tests/UnitTestsParallelizable/Configuration/SourcesManagerTests.cs index 253acc8c0..aa3017d51 100644 --- a/Tests/UnitTestsParallelizable/Configuration/SourcesManagerTests.cs +++ b/Tests/UnitTestsParallelizable/Configuration/SourcesManagerTests.cs @@ -218,6 +218,33 @@ public class SourcesManagerTests Assert.Contains (source, sourcesManager.Sources.Values); } + + //[Fact] + //public void Update_WithValidJson_UpdatesThemeScope () + //{ + // // Arrange + // var sourcesManager = new SourcesManager (); + // var themeScope = new ThemeScope (); + // themeScope.LoadHardCodedDefaults (); + // themeScope ["Button.DefaultShadowStyle"].PropertyValue = ShadowStyle.Opaque; + + // var json = """ + // { + // "Button.DefaultShadowStyle": "None" + // } + // """; + // var source = "test.json"; + // var location = ConfigLocations.HardCoded; + + // // Act + // bool result = sourcesManager.Load (themeScope, json, source, location); + + // // Assert + // Assert.True (result); + // Assert.Equal (Key.Z.WithCtrl, themeScope ["Application.QuitKey"].PropertyValue as Key); + // Assert.Contains (source, sourcesManager.Sources.Values); + //} + #endregion #region Load diff --git a/Tests/UnitTestsParallelizable/Drawing/RulerTests.cs b/Tests/UnitTestsParallelizable/Drawing/RulerTests.cs index cb059d639..9e2d9d320 100644 --- a/Tests/UnitTestsParallelizable/Drawing/RulerTests.cs +++ b/Tests/UnitTestsParallelizable/Drawing/RulerTests.cs @@ -54,7 +54,7 @@ public class RulerTests (ITestOutputHelper output) : FakeDriverBase IDriver driver = CreateFakeDriver (); var r = new Ruler (); - r.Draw (Point.Empty, driver: driver); + r.Draw (driver: driver, location: Point.Empty); DriverAssert.AssertDriverContentsWithFrameAre (@"", output, driver); } @@ -69,7 +69,7 @@ public class RulerTests (ITestOutputHelper output) : FakeDriverBase Assert.Equal (Orientation.Horizontal, r.Orientation); r.Length = len; - r.Draw (Point.Empty, driver: driver); + r.Draw (driver: driver, location: Point.Empty); DriverAssert.AssertDriverContentsWithFrameAre ( @" @@ -79,7 +79,7 @@ public class RulerTests (ITestOutputHelper output) : FakeDriverBase ); // Postive offset - r.Draw (new (1, 1), driver: driver); + r.Draw (driver: driver, location: new (1, 1)); DriverAssert.AssertDriverContentsAre ( @" @@ -91,7 +91,7 @@ public class RulerTests (ITestOutputHelper output) : FakeDriverBase ); // Negative offset - r.Draw (new (-1, 3), driver: driver); + r.Draw (driver: driver, location: new (-1, 3)); DriverAssert.AssertDriverContentsAre ( @" @@ -114,7 +114,7 @@ public class RulerTests (ITestOutputHelper output) : FakeDriverBase var r = new Ruler (); r.Orientation = Orientation.Vertical; r.Length = len; - r.Draw (Point.Empty, driver: driver); + r.Draw (driver: driver, location: Point.Empty); DriverAssert.AssertDriverContentsWithFrameAre ( @" @@ -137,7 +137,7 @@ public class RulerTests (ITestOutputHelper output) : FakeDriverBase driver ); - r.Draw (new (1, 1), driver: driver); + r.Draw (driver: driver, location: new (1, 1)); DriverAssert.AssertDriverContentsWithFrameAre ( @" @@ -162,7 +162,7 @@ public class RulerTests (ITestOutputHelper output) : FakeDriverBase ); // Negative offset - r.Draw (new (2, -1), driver: driver); + r.Draw (driver: driver, location: new (2, -1)); DriverAssert.AssertDriverContentsWithFrameAre ( @" diff --git a/Tests/UnitTestsParallelizable/Drawing/ThicknessTests.cs b/Tests/UnitTestsParallelizable/Drawing/ThicknessTests.cs index a5cfc0436..65105cb89 100644 --- a/Tests/UnitTestsParallelizable/Drawing/ThicknessTests.cs +++ b/Tests/UnitTestsParallelizable/Drawing/ThicknessTests.cs @@ -1,4 +1,5 @@ using System.Text; +using Terminal.Gui.Drivers; using UnitTests; using Xunit.Abstractions; @@ -634,7 +635,7 @@ public class ThicknessTests (ITestOutputHelper output) : FakeDriverBase new (0, 0, driver!.Cols, driver!.Rows), (Rune)' ' ); - t.Draw (r, ViewDiagnosticFlags.Thickness, "Test", driver); + t.Draw (driver, r, ViewDiagnosticFlags.Thickness, "Test"); DriverAssert.AssertDriverContentsWithFrameAre ( @" @@ -650,7 +651,7 @@ public class ThicknessTests (ITestOutputHelper output) : FakeDriverBase new (0, 0, driver!.Cols, driver!.Rows), (Rune)' ' ); - t.Draw (r, ViewDiagnosticFlags.Thickness, "Test", driver); + t.Draw (driver, r, ViewDiagnosticFlags.Thickness, "Test"); DriverAssert.AssertDriverContentsWithFrameAre ( @" @@ -680,7 +681,7 @@ public class ThicknessTests (ITestOutputHelper output) : FakeDriverBase new (0, 0, driver!.Cols, driver!.Rows), (Rune)' ' ); - t.Draw (r, ViewDiagnosticFlags.Thickness, "Test", driver); + t.Draw (driver, r, ViewDiagnosticFlags.Thickness, "Test"); DriverAssert.AssertDriverContentsWithFrameAre ( @" @@ -710,7 +711,7 @@ public class ThicknessTests (ITestOutputHelper output) : FakeDriverBase new (0, 0, driver!.Cols, driver!.Rows), (Rune)' ' ); - t.Draw (r, ViewDiagnosticFlags.Thickness, "Test", driver); + t.Draw (driver, r, ViewDiagnosticFlags.Thickness, "Test"); DriverAssert.AssertDriverContentsWithFrameAre ( @" @@ -754,7 +755,8 @@ public class ThicknessTests (ITestOutputHelper output) : FakeDriverBase var r = new Rectangle (2, 2, 40, 15); top.Draw (); - t.Draw (r, ViewDiagnosticFlags.Ruler, "Test", driver); + top.SetClipToScreen (); + t.Draw (driver, r, ViewDiagnosticFlags.Ruler, "Test"); DriverAssert.AssertDriverContentsAre ( @" @@ -786,7 +788,8 @@ public class ThicknessTests (ITestOutputHelper output) : FakeDriverBase r = new (1, 1, 40, 15); top.SetNeedsDraw (); top.Draw (); - t.Draw (r, ViewDiagnosticFlags.Ruler, "Test", driver); + top.SetClipToScreen (); + t.Draw (driver, r, ViewDiagnosticFlags.Ruler, "Test"); DriverAssert.AssertDriverContentsAre ( @" @@ -818,7 +821,8 @@ public class ThicknessTests (ITestOutputHelper output) : FakeDriverBase r = new (2, 2, 40, 15); top.SetNeedsDraw (); top.Draw (); - t.Draw (r, ViewDiagnosticFlags.Ruler, "Test", driver); + top.SetClipToScreen (); + t.Draw (driver, r, ViewDiagnosticFlags.Ruler, "Test"); DriverAssert.AssertDriverContentsWithFrameAre ( @" @@ -850,7 +854,8 @@ public class ThicknessTests (ITestOutputHelper output) : FakeDriverBase r = new (5, 5, 40, 15); top.SetNeedsDraw (); top.Draw (); - t.Draw (r, ViewDiagnosticFlags.Ruler, "Test", driver); + top.SetClipToScreen (); + t.Draw (driver, r, ViewDiagnosticFlags.Ruler, "Test"); DriverAssert.AssertDriverContentsWithFrameAre ( @" diff --git a/Tests/UnitTestsParallelizable/Drivers/AnsiRequestSchedulerTests.cs b/Tests/UnitTestsParallelizable/Drivers/AnsiRequestSchedulerTests.cs index d643c5112..aafa8243f 100644 --- a/Tests/UnitTestsParallelizable/Drivers/AnsiRequestSchedulerTests.cs +++ b/Tests/UnitTestsParallelizable/Drivers/AnsiRequestSchedulerTests.cs @@ -34,7 +34,7 @@ public class AnsiRequestSchedulerTests _parserMock.Setup (p => p.ExpectResponse ("c", It.IsAny> (), null, false)).Verifiable (Times.Once); // Act - bool result = _scheduler.SendOrSchedule (request); + bool result = _scheduler.SendOrSchedule (null, request); // Assert Assert.Empty (_scheduler.QueuedRequests); // We sent it i.e. we did not queue it for later @@ -57,7 +57,7 @@ public class AnsiRequestSchedulerTests _parserMock.Setup (p => p.IsExpecting ("c")).Returns (true).Verifiable (Times.Once); // Act - bool result = _scheduler.SendOrSchedule (request1); + bool result = _scheduler.SendOrSchedule (null, request1); // Assert Assert.Single (_scheduler.QueuedRequests); // Ensure only one request is in the queue @@ -80,7 +80,7 @@ public class AnsiRequestSchedulerTests _parserMock.Setup (p => p.IsExpecting ("c")).Returns (false).Verifiable (Times.Exactly (2)); _parserMock.Setup (p => p.ExpectResponse ("c", It.IsAny> (), null, false)).Verifiable (Times.Exactly (2)); - _scheduler.SendOrSchedule (request); + _scheduler.SendOrSchedule (null, request); // Simulate time passing beyond throttle SetTime (101); // Exceed throttle limit @@ -88,7 +88,7 @@ public class AnsiRequestSchedulerTests // Act // Send another request after the throttled time limit - bool result = _scheduler.SendOrSchedule (request); + bool result = _scheduler.SendOrSchedule (null, request); // Assert Assert.Empty (_scheduler.QueuedRequests); // Should send and clear the request @@ -111,7 +111,7 @@ public class AnsiRequestSchedulerTests _parserMock.Setup (p => p.IsExpecting ("c")).Returns (false).Verifiable (Times.Exactly (2)); _parserMock.Setup (p => p.ExpectResponse ("c", It.IsAny> (), null, false)).Verifiable (Times.Exactly (2)); - _scheduler.SendOrSchedule (request); + _scheduler.SendOrSchedule (null, request); // Simulate time passing SetTime (55); // Does not exceed throttle limit @@ -119,24 +119,24 @@ public class AnsiRequestSchedulerTests // Act // Send another request after the throttled time limit - bool result = _scheduler.SendOrSchedule (request); + bool result = _scheduler.SendOrSchedule (null, request); // Assert Assert.Single (_scheduler.QueuedRequests); // Should have been queued Assert.False (result); // Should have been queued // Throttle still not exceeded - Assert.False (_scheduler.RunSchedule ()); + Assert.False (_scheduler.RunSchedule (null)); SetTime (90); // Throttle still not exceeded - Assert.False (_scheduler.RunSchedule ()); + Assert.False (_scheduler.RunSchedule (null)); SetTime (105); // Throttle exceeded - so send the request - Assert.True (_scheduler.RunSchedule ()); + Assert.True (_scheduler.RunSchedule (null)); _parserMock.Verify (); } @@ -156,13 +156,13 @@ public class AnsiRequestSchedulerTests _parserMock.Setup (p => p.IsExpecting ("c")).Returns (false).Verifiable (Times.Once); _parserMock.Setup (p => p.ExpectResponse ("c", It.IsAny> (), null, false)).Verifiable (Times.Exactly (2)); - Assert.True (_scheduler.SendOrSchedule (request1)); + Assert.True (_scheduler.SendOrSchedule (null, request1)); // Parser already has an ongoing request for "c" _parserMock.Setup (p => p.IsExpecting ("c")).Returns (true).Verifiable (Times.Exactly (2)); // Cannot send because there is already outstanding request - Assert.False (_scheduler.SendOrSchedule (request1)); + Assert.False (_scheduler.SendOrSchedule (null, request1)); Assert.Single (_scheduler.QueuedRequests); // Simulate request going stale @@ -178,7 +178,7 @@ public class AnsiRequestSchedulerTests .Verifiable (); // When we send again the evicted one should be - bool evicted = _scheduler.RunSchedule (); + bool evicted = _scheduler.RunSchedule (null); Assert.True (evicted); // Stale request should be evicted Assert.Empty (_scheduler.QueuedRequests); @@ -191,7 +191,7 @@ public class AnsiRequestSchedulerTests public void RunSchedule_DoesNothing_WhenQueueIsEmpty () { // Act - bool result = _scheduler.RunSchedule (); + bool result = _scheduler.RunSchedule (null); // Assert Assert.False (result); // No requests to process @@ -213,8 +213,8 @@ public class AnsiRequestSchedulerTests _parserMock.Setup (p => p.ExpectResponse ("x", It.IsAny> (), null, false)).Verifiable (Times.Once); // Act - bool a = _scheduler.SendOrSchedule (request1); - bool b = _scheduler.SendOrSchedule (request2); + bool a = _scheduler.SendOrSchedule (null, request1); + bool b = _scheduler.SendOrSchedule (null, request2); // Assert Assert.False (a); diff --git a/Tests/UnitTestsParallelizable/Drivers/ToAnsiTests.cs b/Tests/UnitTestsParallelizable/Drivers/ToAnsiTests.cs new file mode 100644 index 000000000..e17c28cdf --- /dev/null +++ b/Tests/UnitTestsParallelizable/Drivers/ToAnsiTests.cs @@ -0,0 +1,342 @@ +using System.Text; +using UnitTests; +using Xunit.Abstractions; + +namespace UnitTests_Parallelizable.DriverTests; + +/// +/// Tests for the ToAnsi functionality that generates ANSI escape sequences from buffer contents. +/// +public class ToAnsiTests : FakeDriverBase +{ + [Fact] + public void ToAnsi_Empty_Buffer () + { + IDriver driver = CreateFakeDriver (10, 5); + string ansi = driver.ToAnsi (); + + // Empty buffer should have newlines for each row + Assert.Contains ("\n", ansi); + // Should have 5 newlines (one per row) + Assert.Equal (5, ansi.Count (c => c == '\n')); + } + + [Fact] + public void ToAnsi_Simple_Text () + { + IDriver driver = CreateFakeDriver (10, 3); + driver.AddStr ("Hello"); + driver.Move (0, 1); + driver.AddStr ("World"); + + string ansi = driver.ToAnsi (); + + // Should contain the text + Assert.Contains ("Hello", ansi); + Assert.Contains ("World", ansi); + + // Should have proper structure with newlines + string[] lines = ansi.Split (['\r', '\n'], StringSplitOptions.RemoveEmptyEntries); + Assert.Equal (3, lines.Length); + } + + [Fact] + public void ToAnsi_With_Colors () + { + IDriver driver = CreateFakeDriver (10, 2); + + // Set red foreground + driver.CurrentAttribute = new Attribute (Color.Red, Color.Black); + driver.AddStr ("Red"); + driver.Move (0, 1); + + // Set blue foreground + driver.CurrentAttribute = new Attribute (Color.Blue, Color.Black); + driver.AddStr ("Blue"); + + string ansi = driver.ToAnsi (); + + // Should contain ANSI color codes + Assert.Contains ("\u001b[31m", ansi); // Red foreground + Assert.Contains ("\u001b[34m", ansi); // Blue foreground + Assert.Contains ("Red", ansi); + Assert.Contains ("Blue", ansi); + } + + [Fact] + public void ToAnsi_With_Background_Colors () + { + IDriver driver = CreateFakeDriver (10, 2); + + // Set background color + driver.CurrentAttribute = new Attribute (Color.White, Color.Red); + driver.AddStr ("WhiteOnRed"); + + string ansi = driver.ToAnsi (); + + // Should contain ANSI background color code + Assert.Contains ("\u001b[41m", ansi); // Red background + Assert.Contains ("WhiteOnRed", ansi); + } + + [Fact] + public void ToAnsi_With_Text_Styles () + { + IDriver driver = CreateFakeDriver (10, 3); + + // Bold text + driver.CurrentAttribute = new Attribute (Color.White, Color.Black, TextStyle.Bold); + driver.AddStr ("Bold"); + driver.Move (0, 1); + + // Italic text + driver.CurrentAttribute = new Attribute (Color.White, Color.Black, TextStyle.Italic); + driver.AddStr ("Italic"); + driver.Move (0, 2); + + // Underline text + driver.CurrentAttribute = new Attribute (Color.White, Color.Black, TextStyle.Underline); + driver.AddStr ("Underline"); + + string ansi = driver.ToAnsi (); + + // Should contain ANSI style codes + Assert.Contains ("\u001b[1m", ansi); // Bold + Assert.Contains ("\u001b[3m", ansi); // Italic + Assert.Contains ("\u001b[4m", ansi); // Underline + } + + [Fact] + public void ToAnsi_With_Wide_Characters () + { + IDriver driver = CreateFakeDriver (10, 2); + + // Add a wide character (Chinese character) + driver.AddStr ("??"); + driver.Move (0, 1); + driver.AddStr ("??"); + + string ansi = driver.ToAnsi (); + + Assert.Contains ("??", ansi); + Assert.Contains ("??", ansi); + } + + [Fact] + public void ToAnsi_With_Unicode_Characters () + { + IDriver driver = CreateFakeDriver (10, 2); + + // Add various Unicode characters + driver.AddStr ("???"); // Greek letters + driver.Move (0, 1); + driver.AddStr ("???"); // Emoji + + string ansi = driver.ToAnsi (); + + Assert.Contains ("???", ansi); + Assert.Contains ("???", ansi); + } + + [Fact] + public void ToAnsi_Attribute_Changes_Within_Line () + { + IDriver driver = CreateFakeDriver (20, 1); + + driver.AddStr ("Normal"); + driver.CurrentAttribute = new Attribute (Color.Red, Color.Black); + driver.AddStr ("Red"); + driver.CurrentAttribute = new Attribute (Color.Blue, Color.Black); + driver.AddStr ("Blue"); + + string ansi = driver.ToAnsi (); + + // Should contain color changes within the line + Assert.Contains ("Normal", ansi); + Assert.Contains ("\u001b[31m", ansi); // Red + Assert.Contains ("\u001b[34m", ansi); // Blue + } + + [Fact] + public void ToAnsi_Large_Buffer () + { + // Test with a larger buffer to stress performance + IDriver driver = CreateFakeDriver (200, 50); + + // Fill with some content + for (int row = 0; row < 50; row++) + { + driver.Move (0, row); + driver.CurrentAttribute = new Attribute ((ColorName16)(row % 16), Color.Black); + driver.AddStr ($"Row {row:D2} content"); + } + + string ansi = driver.ToAnsi (); + + // Should contain all rows + Assert.Contains ("Row 00", ansi); + Assert.Contains ("Row 49", ansi); + + // Should have proper newlines (50 content lines + 50 newlines) + Assert.Equal (50, ansi.Count (c => c == '\n')); + } + + [Fact] + public void ToAnsi_RGB_Colors () + { + IDriver driver = CreateFakeDriver (10, 1); + + // Use RGB colors (when not forcing 16 colors) + Application.Force16Colors = false; + try + { + driver.CurrentAttribute = new Attribute (new Color (255, 0, 0), new Color (0, 255, 0)); + driver.AddStr ("RGB"); + + string ansi = driver.ToAnsi (); + + // Should contain RGB color codes + Assert.Contains ("\u001b[38;2;255;0;0m", ansi); // Red foreground RGB + Assert.Contains ("\u001b[48;2;0;255;0m", ansi); // Green background RGB + } + finally + { + Application.Force16Colors = true; // Reset + } + } + + [Fact] + public void ToAnsi_Force16Colors () + { + IDriver driver = CreateFakeDriver (10, 1); + + // Force 16 colors + Application.Force16Colors = true; + driver.CurrentAttribute = new Attribute (Color.Red, Color.Blue); + driver.AddStr ("16Color"); + + string ansi = driver.ToAnsi (); + + // Should contain 16-color codes, not RGB + Assert.Contains ("\u001b[31m", ansi); // Red foreground (16-color) + Assert.Contains ("\u001b[44m", ansi); // Blue background (16-color) + Assert.DoesNotContain ("\u001b[38;2;", ansi); // No RGB codes + } + + [Fact] + public void ToAnsi_Multiple_Attributes_Per_Line () + { + IDriver driver = CreateFakeDriver (50, 1); + + // Create a line with many attribute changes + string[] colors = { "Red", "Green", "Blue", "Yellow", "Magenta", "Cyan" }; + + foreach (string colorName in colors) + { + Color fg = colorName switch + { + "Red" => Color.Red, + "Green" => Color.Green, + "Blue" => Color.Blue, + "Yellow" => Color.Yellow, + "Magenta" => Color.Magenta, + "Cyan" => Color.Cyan, + _ => Color.White + }; + + driver.CurrentAttribute = new Attribute (fg, Color.Black); + driver.AddStr (colorName); + } + + string ansi = driver.ToAnsi (); + + // Should contain multiple color codes + Assert.Contains ("\u001b[31m", ansi); // Red + Assert.Contains ("\u001b[32m", ansi); // Green + Assert.Contains ("\u001b[34m", ansi); // Blue + Assert.Contains ("\u001b[33m", ansi); // Yellow + Assert.Contains ("\u001b[35m", ansi); // Magenta + Assert.Contains ("\u001b[36m", ansi); // Cyan + } + + [Fact] + public void ToAnsi_Special_Characters () + { + IDriver driver = CreateFakeDriver (20, 1); + + // Test backslash character + driver.AddStr ("Backslash:"); + driver.AddRune ('\\'); + + string ansi = driver.ToAnsi (); + + Assert.Contains ("Backslash:", ansi); + Assert.Contains ("\\", ansi); + } + + [Fact] + public void ToAnsi_Buffer_Boundary_Conditions () + { + // Test with minimum buffer size + IDriver driver = CreateFakeDriver (1, 1); + driver.AddStr ("X"); + + string ansi = driver.ToAnsi (); + + Assert.Contains ("X", ansi); + Assert.Contains ("\n", ansi); + + // Test with very wide buffer + driver = CreateFakeDriver (1000, 1); + driver.AddStr ("Wide"); + + ansi = driver.ToAnsi (); + + Assert.Contains ("Wide", ansi); + Assert.True (ansi.Length > 1000); // Should have many spaces + } + + [Fact] + public void ToAnsi_Empty_Lines () + { + IDriver driver = CreateFakeDriver (10, 3); + + // Only write to first and third lines + driver.AddStr ("First"); + driver.Move (0, 2); + driver.AddStr ("Third"); + + string ansi = driver.ToAnsi (); + + string[] lines = ansi.Split ('\n'); + Assert.Equal (4, lines.Length); // 3 content lines + 1 empty line at end + Assert.Contains ("First", lines[0]); + Assert.Contains ("Third", lines[2]); + } + + [Fact] + public void ToAnsi_Performance_Stress_Test () + { + // Create a large buffer and fill it completely + const int width = 200; + const int height = 100; + IDriver driver = CreateFakeDriver (width, height); + + // Fill every cell with different content and colors + for (int row = 0; row < height; row++) + { + for (int col = 0; col < width; col++) + { + driver.Move (col, row); + driver.CurrentAttribute = new Attribute ((ColorName16)((row + col) % 16), Color.Black); + driver.AddRune ((char)('A' + ((row + col) % 26))); + } + } + + // This should complete in reasonable time and not throw + string ansi = driver.ToAnsi (); + + Assert.NotNull (ansi); + Assert.True (ansi.Length > width * height); // Should contain all characters plus ANSI codes + } +} \ No newline at end of file diff --git a/Tests/UnitTestsParallelizable/TestSetup.cs b/Tests/UnitTestsParallelizable/TestSetup.cs index 2a6402c11..a8d37578e 100644 --- a/Tests/UnitTestsParallelizable/TestSetup.cs +++ b/Tests/UnitTestsParallelizable/TestSetup.cs @@ -39,7 +39,7 @@ public class GlobalTestSetup : IDisposable // Check that all Application fields and properties are set to their default values // Public Properties - Assert.Null (Application.Top); + Assert.Null (Application.Current); Assert.Null (Application.Mouse.MouseGrabView); // Don't check Application.ForceDriver @@ -59,7 +59,7 @@ public class GlobalTestSetup : IDisposable Assert.Equal (Application.GetSupportedCultures (), Application.SupportedCultures); Assert.Equal (Application.GetAvailableCulturesFromEmbeddedResources (), Application.SupportedCultures); Assert.Null (Application.MainThreadId); - Assert.Empty (Application.TopLevels); + Assert.Empty (Application.SessionStack); Assert.Empty (Application.CachedViewsUnderMouse); // Mouse @@ -67,10 +67,10 @@ public class GlobalTestSetup : IDisposable //Assert.Null (Application._lastMousePosition); // Navigation - Assert.Null (Application.Navigation); + // Assert.Null (Application.Navigation); // Popover - Assert.Null (Application.Popover); + //Assert.Null (Application.Popover); // Events - Can't check //Assert.Null (Application.SessionBegun); diff --git a/Tests/UnitTestsParallelizable/Text/TextFormatterDrawTests.cs b/Tests/UnitTestsParallelizable/Text/TextFormatterDrawTests.cs index 0c142f08b..17ac27ab5 100644 --- a/Tests/UnitTestsParallelizable/Text/TextFormatterDrawTests.cs +++ b/Tests/UnitTestsParallelizable/Text/TextFormatterDrawTests.cs @@ -36,7 +36,7 @@ public class TextFormatterDrawTests (ITestOutputHelper output) : FakeDriverBase tf.ConstrainToWidth = width; tf.ConstrainToHeight = height; - tf.Draw (new (0, 0, width, height), Attribute.Default, Attribute.Default, driver: driver); + tf.Draw (driver: driver, screen: new (0, 0, width, height), normalColor: Attribute.Default, hotColor: Attribute.Default); DriverAssert.AssertDriverContentsWithFrameAre (expectedText, output, driver); } @@ -65,7 +65,7 @@ public class TextFormatterDrawTests (ITestOutputHelper output) : FakeDriverBase tf.ConstrainToWidth = width; tf.ConstrainToHeight = height; - tf.Draw (new (0, 0, width, height), Attribute.Default, Attribute.Default, driver: driver); + tf.Draw (driver: driver, screen: new (0, 0, width, height), normalColor: Attribute.Default, hotColor: Attribute.Default); DriverAssert.AssertDriverContentsWithFrameAre (expectedText, output, driver); } @@ -105,7 +105,7 @@ public class TextFormatterDrawTests (ITestOutputHelper output) : FakeDriverBase tf.ConstrainToWidth = width; tf.ConstrainToHeight = height; - tf.Draw (new (Point.Empty, new (width, height)), Attribute.Default, Attribute.Default, driver: driver); + tf.Draw (driver: driver, screen: new (Point.Empty, new (width, height)), normalColor: Attribute.Default, hotColor: Attribute.Default); Rectangle rect = DriverAssert.AssertDriverContentsWithFrameAre (expectedText, output, driver); Assert.Equal (expectedY, rect.Y); } @@ -134,7 +134,7 @@ public class TextFormatterDrawTests (ITestOutputHelper output) : FakeDriverBase tf.ConstrainToWidth = width; tf.ConstrainToHeight = height; - tf.Draw (new (0, 0, width, height), Attribute.Default, Attribute.Default, driver: driver); + tf.Draw (driver: driver, screen: new (0, 0, width, height), normalColor: Attribute.Default, hotColor: Attribute.Default); DriverAssert.AssertDriverContentsWithFrameAre (expectedText, output, driver); } @@ -163,7 +163,7 @@ public class TextFormatterDrawTests (ITestOutputHelper output) : FakeDriverBase tf.ConstrainToWidth = width; tf.ConstrainToHeight = height; - tf.Draw (new (0, 0, width, height), Attribute.Default, Attribute.Default, driver: driver); + tf.Draw (driver: driver, screen: new (0, 0, width, height), normalColor: Attribute.Default, hotColor: Attribute.Default); DriverAssert.AssertDriverContentsWithFrameAre (expectedText, output, driver); } @@ -217,7 +217,7 @@ s")] tf.ConstrainToWidth = width; tf.ConstrainToHeight = height; - tf.Draw (new (0, 0, 20, 20), Attribute.Default, Attribute.Default, driver: driver); + tf.Draw (driver: driver, screen: new (0, 0, 20, 20), normalColor: Attribute.Default, hotColor: Attribute.Default); DriverAssert.AssertDriverContentsWithFrameAre (expectedText, output, driver); } @@ -267,7 +267,7 @@ s")] tf.ConstrainToWidth = width; tf.ConstrainToHeight = height; - tf.Draw (new (0, 0, 5, height), Attribute.Default, Attribute.Default, driver: driver); + tf.Draw (driver: driver, screen: new (0, 0, 5, height), normalColor: Attribute.Default, hotColor: Attribute.Default); Rectangle rect = DriverAssert.AssertDriverContentsWithFrameAre (expectedText, output, driver); Assert.Equal (expectedY, rect.Y); @@ -328,7 +328,7 @@ B ")] tf.ConstrainToWidth = 5; tf.ConstrainToHeight = height; - tf.Draw (new (0, 0, 5, height), Attribute.Default, Attribute.Default, driver: driver); + tf.Draw (driver: driver, screen: new (0, 0, 5, height), normalColor: Attribute.Default, hotColor: Attribute.Default); DriverAssert.AssertDriverContentsWithFrameAre (expectedText, output, driver); } @@ -346,11 +346,7 @@ B ")] }; var tf = new TextFormatter { ConstrainToSize = new (14, 3), Text = "Test\nTest long\nTest long long\n", MultiLine = true }; - tf.Draw ( - new (1, 1, 19, 3), - attrs [1], - attrs [2], - driver: driver); + tf.Draw (driver: driver, screen: new (1, 1, 19, 3), normalColor: attrs [1], hotColor: attrs [2]); Assert.False (tf.FillRemaining); @@ -375,11 +371,7 @@ B ")] tf.FillRemaining = true; - tf.Draw ( - new (1, 1, 19, 3), - attrs [1], - attrs [2], - driver: driver); + tf.Draw (driver: driver, screen: new (1, 1, 19, 3), normalColor: attrs [1], hotColor: attrs [2]); DriverAssert.AssertDriverAttributesAre ( @" @@ -422,7 +414,7 @@ Nice Work")] MultiLine = true }; - tf.Draw (new (0, 0, width, height), Attribute.Default, Attribute.Default, driver: driver); + tf.Draw (driver: driver, screen: new (0, 0, width, height), normalColor: Attribute.Default, hotColor: Attribute.Default); DriverAssert.AssertDriverContentsWithFrameAre (expectedText, output, driver); } @@ -448,7 +440,7 @@ Nice Work")] driver!.SetScreenSize (tfSize.Width, tfSize.Height); driver.FillRect (driver.Screen, (Rune)'*'); - tf.Draw (driver.Screen, Attribute.Default, Attribute.Default, driver: driver); + tf.Draw (driver: driver, screen: driver.Screen, normalColor: Attribute.Default, hotColor: Attribute.Default); var expectedText = """ UI Catalog: A comprehensive sample library and test app for @@ -575,7 +567,7 @@ Nice Work")] Size size = tf.FormatAndGetSize (); Assert.Equal (new (expectedWidth, expectedHeight), size); - tf.Draw (new (0, 0, width, height), Attribute.Default, Attribute.Default, driver: driver); + tf.Draw (driver: driver, screen: new (0, 0, width, height), normalColor: Attribute.Default, hotColor: Attribute.Default); DriverAssert.AssertDriverContentsWithFrameAre (expectedDraw, output, driver); } @@ -660,7 +652,7 @@ Nice Work")] Size size = tf.FormatAndGetSize (); Assert.Equal (new (expectedWidth, expectedHeight), size); - tf.Draw (new (0, 0, width, height), Attribute.Default, Attribute.Default, driver: driver); + tf.Draw (driver: driver, screen: new (0, 0, width, height), normalColor: Attribute.Default, hotColor: Attribute.Default); DriverAssert.AssertDriverContentsWithFrameAre (expectedDraw, output, driver); } diff --git a/Tests/UnitTestsParallelizable/Text/TextFormatterJustificationTests.cs b/Tests/UnitTestsParallelizable/Text/TextFormatterJustificationTests.cs index 4bae12df3..8990e1698 100644 --- a/Tests/UnitTestsParallelizable/Text/TextFormatterJustificationTests.cs +++ b/Tests/UnitTestsParallelizable/Text/TextFormatterJustificationTests.cs @@ -3423,7 +3423,7 @@ public class TextFormatterJustificationTests (ITestOutputHelper output) : FakeDr }; driver.FillRect (new (0, 0, 7, 7), (Rune)'*'); - tf.Draw (new (0, 0, 7, 7), Attribute.Default, Attribute.Default, driver: driver); + tf.Draw (driver: driver, screen: new (0, 0, 7, 7), normalColor: Attribute.Default, hotColor: Attribute.Default); DriverAssert.AssertDriverContentsWithFrameAre (expectedText, output, driver); } } diff --git a/Tests/UnitTestsParallelizable/Text/TextFormatterTests.cs b/Tests/UnitTestsParallelizable/Text/TextFormatterTests.cs index 88e95fd7f..8fc71180c 100644 --- a/Tests/UnitTestsParallelizable/Text/TextFormatterTests.cs +++ b/Tests/UnitTestsParallelizable/Text/TextFormatterTests.cs @@ -1142,6 +1142,7 @@ public class TextFormatterTests (ITestOutputHelper output) : FakeDriverBase [Fact] public void NeedsFormat_Sets () { + IDriver driver = CreateFakeDriver (); var testText = "test"; var testBounds = new Rectangle (0, 0, 100, 1); var tf = new TextFormatter (); @@ -1151,7 +1152,7 @@ public class TextFormatterTests (ITestOutputHelper output) : FakeDriverBase Assert.NotEmpty (tf.GetLines ()); Assert.False (tf.NeedsFormat); // get_Lines causes a Format Assert.Equal (testText, tf.Text); - tf.Draw (testBounds, new (), new ()); + tf.Draw (driver: driver, screen: testBounds, normalColor: new (), hotColor: new ()); Assert.False (tf.NeedsFormat); tf.ConstrainToSize = new (1, 1); @@ -2987,7 +2988,7 @@ public class TextFormatterTests (ITestOutputHelper output) : FakeDriverBase ConstrainToHeight = 1 }; - tf.Draw (new Rectangle (0, 0, width, 1), Attribute.Default, Attribute.Default, default, driver); + tf.Draw (driver, new Rectangle (0, 0, width, 1), Attribute.Default, Attribute.Default, default); DriverAssert.AssertDriverContentsWithFrameAre (expectedText, output, driver); } @@ -3016,7 +3017,7 @@ public class TextFormatterTests (ITestOutputHelper output) : FakeDriverBase ConstrainToHeight = 1 }; - tf.Draw (new Rectangle (0, 0, width, 1), Attribute.Default, Attribute.Default, default, driver); + tf.Draw (driver, new Rectangle (0, 0, width, 1), Attribute.Default, Attribute.Default, default); DriverAssert.AssertDriverContentsWithFrameAre (expectedText, output, driver); } @@ -3042,7 +3043,7 @@ public class TextFormatterTests (ITestOutputHelper output) : FakeDriverBase ConstrainToHeight = 1 }; - tf.Draw (new Rectangle (0, 0, width, 1), Attribute.Default, Attribute.Default, default, driver); + tf.Draw (driver, new Rectangle (0, 0, width, 1), Attribute.Default, Attribute.Default, default); DriverAssert.AssertDriverContentsWithFrameAre (expectedText, output, driver); } @@ -3068,7 +3069,7 @@ public class TextFormatterTests (ITestOutputHelper output) : FakeDriverBase ConstrainToHeight = 1 }; - tf.Draw (new Rectangle (0, 0, width, 1), Attribute.Default, Attribute.Default, default, driver); + tf.Draw (driver, new Rectangle (0, 0, width, 1), Attribute.Default, Attribute.Default, default); DriverAssert.AssertDriverContentsWithFrameAre (expectedText, output, driver); } @@ -3100,12 +3101,11 @@ ssb tf.ConstrainToSize = new (width, height); tf.Draw ( + driver, new (0, 0, width, height), new (ColorName16.White, ColorName16.Black), new (ColorName16.Blue, ColorName16.Black), - default (Rectangle), - driver - ); + default (Rectangle)); DriverAssert.AssertDriverContentsWithFrameAre (expected, output, driver); driver.End (); @@ -3139,12 +3139,11 @@ ssb Assert.True (tf.WordWrap); tf.Draw ( + driver, new (0, 0, width, height), new (ColorName16.White, ColorName16.Black), new (ColorName16.Blue, ColorName16.Black), - default (Rectangle), - driver - ); + default (Rectangle)); DriverAssert.AssertDriverContentsWithFrameAre (expected, output, driver); driver.End (); @@ -3178,12 +3177,11 @@ ssb Assert.False (tf.PreserveTrailingSpaces); tf.Draw ( + driver, new (0, 0, width, height), new (ColorName16.White, ColorName16.Black), new (ColorName16.Blue, ColorName16.Black), - default (Rectangle), - driver - ); + default (Rectangle)); DriverAssert.AssertDriverContentsWithFrameAre (expected, output, driver); driver.End (); @@ -3217,12 +3215,11 @@ ssb Assert.False (tf.PreserveTrailingSpaces); tf.Draw ( + driver, new (0, 0, width, height), new (ColorName16.White, ColorName16.Black), new (ColorName16.Blue, ColorName16.Black), - default (Rectangle), - driver - ); + default (Rectangle)); DriverAssert.AssertDriverContentsWithFrameAre (expected, output, driver); driver.End (); diff --git a/Tests/UnitTestsParallelizable/UnitTests.Parallelizable.csproj b/Tests/UnitTestsParallelizable/UnitTests.Parallelizable.csproj index cf0f77f7c..35233cc03 100644 --- a/Tests/UnitTestsParallelizable/UnitTests.Parallelizable.csproj +++ b/Tests/UnitTestsParallelizable/UnitTests.Parallelizable.csproj @@ -69,4 +69,7 @@ + + + \ No newline at end of file diff --git a/Tests/UnitTestsParallelizable/View/ArrangementTests.cs b/Tests/UnitTestsParallelizable/View/ArrangementTests.cs index 842a7070b..cc156b796 100644 --- a/Tests/UnitTestsParallelizable/View/ArrangementTests.cs +++ b/Tests/UnitTestsParallelizable/View/ArrangementTests.cs @@ -28,11 +28,11 @@ public class ArrangementTests (ITestOutputHelper output) [Fact] public void ViewArrangement_Resizable_IsCombinationOfAllResizableFlags () { - ViewArrangement expected = ViewArrangement.LeftResizable - | ViewArrangement.RightResizable - | ViewArrangement.TopResizable + ViewArrangement expected = ViewArrangement.LeftResizable + | ViewArrangement.RightResizable + | ViewArrangement.TopResizable | ViewArrangement.BottomResizable; - + Assert.Equal (ViewArrangement.Resizable, expected); } @@ -40,7 +40,7 @@ public class ArrangementTests (ITestOutputHelper output) public void ViewArrangement_CanCombineFlags () { ViewArrangement arrangement = ViewArrangement.Movable | ViewArrangement.LeftResizable; - + Assert.True (arrangement.HasFlag (ViewArrangement.Movable)); Assert.True (arrangement.HasFlag (ViewArrangement.LeftResizable)); Assert.False (arrangement.HasFlag (ViewArrangement.RightResizable)); @@ -67,11 +67,11 @@ public class ArrangementTests (ITestOutputHelper output) [Fact] public void View_Arrangement_CanSetMultipleFlags () { - var view = new View - { - Arrangement = ViewArrangement.Movable | ViewArrangement.Resizable + var view = new View + { + Arrangement = ViewArrangement.Movable | ViewArrangement.Resizable }; - + Assert.True (view.Arrangement.HasFlag (ViewArrangement.Movable)); Assert.True (view.Arrangement.HasFlag (ViewArrangement.LeftResizable)); Assert.True (view.Arrangement.HasFlag (ViewArrangement.RightResizable)); @@ -83,7 +83,7 @@ public class ArrangementTests (ITestOutputHelper output) public void View_Arrangement_Overlapped_CanBeSetIndependently () { var view = new View { Arrangement = ViewArrangement.Overlapped }; - + Assert.True (view.Arrangement.HasFlag (ViewArrangement.Overlapped)); Assert.False (view.Arrangement.HasFlag (ViewArrangement.Movable)); Assert.False (view.Arrangement.HasFlag (ViewArrangement.Resizable)); @@ -92,11 +92,11 @@ public class ArrangementTests (ITestOutputHelper output) [Fact] public void View_Arrangement_CanCombineOverlappedWithOtherFlags () { - var view = new View - { - Arrangement = ViewArrangement.Overlapped | ViewArrangement.Movable + var view = new View + { + Arrangement = ViewArrangement.Overlapped | ViewArrangement.Movable }; - + Assert.True (view.Arrangement.HasFlag (ViewArrangement.Overlapped)); Assert.True (view.Arrangement.HasFlag (ViewArrangement.Movable)); } @@ -108,12 +108,12 @@ public class ArrangementTests (ITestOutputHelper output) [Fact] public void TopResizable_WithoutMovable_IsAllowed () { - var view = new View - { + var view = new View + { Arrangement = ViewArrangement.TopResizable, BorderStyle = LineStyle.Single }; - + Assert.True (view.Arrangement.HasFlag (ViewArrangement.TopResizable)); Assert.False (view.Arrangement.HasFlag (ViewArrangement.Movable)); } @@ -123,16 +123,16 @@ public class ArrangementTests (ITestOutputHelper output) { // According to docs and Border.Arrangment.cs line 569: // TopResizable is only checked if NOT Movable - var view = new View - { + var view = new View + { Arrangement = ViewArrangement.Movable | ViewArrangement.TopResizable, BorderStyle = LineStyle.Single }; - + // Both flags can be set on the property Assert.True (view.Arrangement.HasFlag (ViewArrangement.Movable)); Assert.True (view.Arrangement.HasFlag (ViewArrangement.TopResizable)); - + // But the behavior in Border.DetermineArrangeModeFromClick // will prioritize Movable over TopResizable } @@ -140,12 +140,12 @@ public class ArrangementTests (ITestOutputHelper output) [Fact] public void Resizable_WithMovable_IncludesTopResizable () { - var view = new View - { + var view = new View + { Arrangement = ViewArrangement.Resizable | ViewArrangement.Movable, BorderStyle = LineStyle.Single }; - + Assert.True (view.Arrangement.HasFlag (ViewArrangement.Movable)); Assert.True (view.Arrangement.HasFlag (ViewArrangement.TopResizable)); Assert.True (view.Arrangement.HasFlag (ViewArrangement.LeftResizable)); @@ -160,12 +160,12 @@ public class ArrangementTests (ITestOutputHelper output) [Fact] public void Border_WithNoArrangement_HasNoArrangementOptions () { - var view = new View - { + var view = new View + { Arrangement = ViewArrangement.Fixed, BorderStyle = LineStyle.Single }; - + Assert.NotNull (view.Border); Assert.Equal (ViewArrangement.Fixed, view.Arrangement); } @@ -174,8 +174,8 @@ public class ArrangementTests (ITestOutputHelper output) public void Border_WithMovableArrangement_CanEnterArrangeMode () { var superView = new View (); - var view = new View - { + var view = new View + { Arrangement = ViewArrangement.Movable, BorderStyle = LineStyle.Single, X = 0, @@ -184,7 +184,7 @@ public class ArrangementTests (ITestOutputHelper output) Height = 10 }; superView.Add (view); - + Assert.NotNull (view.Border); Assert.True (view.Arrangement.HasFlag (ViewArrangement.Movable)); } @@ -192,12 +192,12 @@ public class ArrangementTests (ITestOutputHelper output) [Fact] public void Border_WithResizableArrangement_HasResizableOptions () { - var view = new View - { + var view = new View + { Arrangement = ViewArrangement.Resizable, BorderStyle = LineStyle.Single }; - + Assert.NotNull (view.Border); Assert.True (view.Arrangement.HasFlag (ViewArrangement.LeftResizable)); Assert.True (view.Arrangement.HasFlag (ViewArrangement.RightResizable)); @@ -212,15 +212,15 @@ public class ArrangementTests (ITestOutputHelper output) [InlineData (ViewArrangement.BottomResizable)] public void Border_WithSingleResizableDirection_OnlyHasThatOption (ViewArrangement arrangement) { - var view = new View - { + var view = new View + { Arrangement = arrangement, BorderStyle = LineStyle.Single }; - + Assert.NotNull (view.Border); Assert.True (view.Arrangement.HasFlag (arrangement)); - + // Verify other directions are not set if (arrangement != ViewArrangement.LeftResizable) Assert.False (view.Arrangement.HasFlag (ViewArrangement.LeftResizable)); @@ -239,12 +239,12 @@ public class ArrangementTests (ITestOutputHelper output) [Fact] public void Border_BottomRightResizable_CombinesBothFlags () { - var view = new View - { + var view = new View + { Arrangement = ViewArrangement.BottomResizable | ViewArrangement.RightResizable, BorderStyle = LineStyle.Single }; - + Assert.True (view.Arrangement.HasFlag (ViewArrangement.BottomResizable)); Assert.True (view.Arrangement.HasFlag (ViewArrangement.RightResizable)); Assert.False (view.Arrangement.HasFlag (ViewArrangement.TopResizable)); @@ -254,12 +254,12 @@ public class ArrangementTests (ITestOutputHelper output) [Fact] public void Border_BottomLeftResizable_CombinesBothFlags () { - var view = new View - { + var view = new View + { Arrangement = ViewArrangement.BottomResizable | ViewArrangement.LeftResizable, BorderStyle = LineStyle.Single }; - + Assert.True (view.Arrangement.HasFlag (ViewArrangement.BottomResizable)); Assert.True (view.Arrangement.HasFlag (ViewArrangement.LeftResizable)); Assert.False (view.Arrangement.HasFlag (ViewArrangement.TopResizable)); @@ -269,12 +269,12 @@ public class ArrangementTests (ITestOutputHelper output) [Fact] public void Border_TopRightResizable_CombinesBothFlags () { - var view = new View - { + var view = new View + { Arrangement = ViewArrangement.TopResizable | ViewArrangement.RightResizable, BorderStyle = LineStyle.Single }; - + Assert.True (view.Arrangement.HasFlag (ViewArrangement.TopResizable)); Assert.True (view.Arrangement.HasFlag (ViewArrangement.RightResizable)); Assert.False (view.Arrangement.HasFlag (ViewArrangement.BottomResizable)); @@ -284,12 +284,12 @@ public class ArrangementTests (ITestOutputHelper output) [Fact] public void Border_TopLeftResizable_CombinesBothFlags () { - var view = new View - { + var view = new View + { Arrangement = ViewArrangement.TopResizable | ViewArrangement.LeftResizable, BorderStyle = LineStyle.Single }; - + Assert.True (view.Arrangement.HasFlag (ViewArrangement.TopResizable)); Assert.True (view.Arrangement.HasFlag (ViewArrangement.LeftResizable)); Assert.False (view.Arrangement.HasFlag (ViewArrangement.BottomResizable)); @@ -326,8 +326,8 @@ public class ArrangementTests (ITestOutputHelper output) [Fact] public void Overlapped_AllowsSubViewsToOverlap () { - var superView = new View - { + var superView = new View + { Arrangement = ViewArrangement.Overlapped, Width = 20, Height = 20 @@ -351,7 +351,7 @@ public class ArrangementTests (ITestOutputHelper output) public void LeftResizable_CanBeUsedForHorizontalSplitter () { var container = new View { Width = 80, Height = 25 }; - + var leftPane = new View { X = 0, @@ -359,7 +359,7 @@ public class ArrangementTests (ITestOutputHelper output) Width = 40, Height = Dim.Fill () }; - + var rightPane = new View { X = 40, @@ -369,9 +369,9 @@ public class ArrangementTests (ITestOutputHelper output) Arrangement = ViewArrangement.LeftResizable, BorderStyle = LineStyle.Single }; - + container.Add (leftPane, rightPane); - + Assert.True (rightPane.Arrangement.HasFlag (ViewArrangement.LeftResizable)); Assert.NotNull (rightPane.Border); } @@ -380,7 +380,7 @@ public class ArrangementTests (ITestOutputHelper output) public void TopResizable_CanBeUsedForVerticalSplitter () { var container = new View { Width = 80, Height = 25 }; - + var topPane = new View { X = 0, @@ -388,7 +388,7 @@ public class ArrangementTests (ITestOutputHelper output) Width = Dim.Fill (), Height = 10 }; - + var bottomPane = new View { X = 0, @@ -398,9 +398,9 @@ public class ArrangementTests (ITestOutputHelper output) Arrangement = ViewArrangement.TopResizable, BorderStyle = LineStyle.Single }; - + container.Add (topPane, bottomPane); - + Assert.True (bottomPane.Arrangement.HasFlag (ViewArrangement.TopResizable)); Assert.NotNull (bottomPane.Border); } @@ -412,11 +412,11 @@ public class ArrangementTests (ITestOutputHelper output) [Fact] public void View_WithoutBorderStyle_CanHaveArrangement () { - var view = new View - { + var view = new View + { Arrangement = ViewArrangement.Movable }; - + // Arrangement can be set even without a border style // Border object still exists but has no visible style Assert.Equal (ViewArrangement.Movable, view.Arrangement); @@ -427,11 +427,11 @@ public class ArrangementTests (ITestOutputHelper output) [Fact] public void View_WithNoBorderStyle_ResizableCanBeSet () { - var view = new View - { + var view = new View + { Arrangement = ViewArrangement.Resizable }; - + // Arrangement is set but has limited effect without a visible border style Assert.Equal (ViewArrangement.Resizable, view.Arrangement); Assert.NotNull (view.Border); @@ -448,8 +448,8 @@ public class ArrangementTests (ITestOutputHelper output) // This test verifies the documented behavior that TopResizable is ignored // when Movable is also set (line 569 in Border.Arrangment.cs) var superView = new View { Width = 80, Height = 25 }; - var view = new View - { + var view = new View + { Arrangement = ViewArrangement.TopResizable | ViewArrangement.Movable, BorderStyle = LineStyle.Single, X = 10, @@ -458,11 +458,11 @@ public class ArrangementTests (ITestOutputHelper output) Height = 10 }; superView.Add (view); - + // The view has both flags set Assert.True (view.Arrangement.HasFlag (ViewArrangement.TopResizable)); Assert.True (view.Arrangement.HasFlag (ViewArrangement.Movable)); - + // But Movable takes precedence in Border.DetermineArrangeModeFromClick // This is verified by the code at line 569 checking !Parent!.Arrangement.HasFlag(ViewArrangement.Movable) } @@ -471,8 +471,8 @@ public class ArrangementTests (ITestOutputHelper output) public void DetermineArrangeModeFromClick_TopResizableWorksWithoutMovable () { var superView = new View { Width = 80, Height = 25 }; - var view = new View - { + var view = new View + { Arrangement = ViewArrangement.TopResizable, BorderStyle = LineStyle.Single, X = 10, @@ -481,7 +481,7 @@ public class ArrangementTests (ITestOutputHelper output) Height = 10 }; superView.Add (view); - + // Only TopResizable is set Assert.True (view.Arrangement.HasFlag (ViewArrangement.TopResizable)); Assert.False (view.Arrangement.HasFlag (ViewArrangement.Movable)); @@ -491,20 +491,20 @@ public class ArrangementTests (ITestOutputHelper output) public void DetermineArrangeModeFromClick_AllCornerCombinationsSupported () { var superView = new View { Width = 80, Height = 25 }; - + // Test that all 4 corner combinations are recognized - var cornerCombinations = new[] + var cornerCombinations = new [] { ViewArrangement.BottomResizable | ViewArrangement.RightResizable, ViewArrangement.BottomResizable | ViewArrangement.LeftResizable, ViewArrangement.TopResizable | ViewArrangement.RightResizable, ViewArrangement.TopResizable | ViewArrangement.LeftResizable }; - + foreach (var arrangement in cornerCombinations) { - var view = new View - { + var view = new View + { Arrangement = arrangement, BorderStyle = LineStyle.Single, X = 10, @@ -513,10 +513,10 @@ public class ArrangementTests (ITestOutputHelper output) Height = 10 }; superView.Add (view); - + // Verify the flags are set correctly Assert.True (view.Arrangement == arrangement); - + superView.Remove (view); } } @@ -530,10 +530,10 @@ public class ArrangementTests (ITestOutputHelper output) { var view = new View { Arrangement = ViewArrangement.Fixed }; Assert.Equal (ViewArrangement.Fixed, view.Arrangement); - + view.Arrangement = ViewArrangement.Movable; Assert.Equal (ViewArrangement.Movable, view.Arrangement); - + view.Arrangement = ViewArrangement.Resizable; Assert.Equal (ViewArrangement.Resizable, view.Arrangement); } @@ -542,7 +542,7 @@ public class ArrangementTests (ITestOutputHelper output) public void View_Arrangement_CanAddFlags () { var view = new View { Arrangement = ViewArrangement.Movable }; - + view.Arrangement |= ViewArrangement.LeftResizable; Assert.True (view.Arrangement.HasFlag (ViewArrangement.Movable)); Assert.True (view.Arrangement.HasFlag (ViewArrangement.LeftResizable)); @@ -551,11 +551,11 @@ public class ArrangementTests (ITestOutputHelper output) [Fact] public void View_Arrangement_CanRemoveFlags () { - var view = new View - { - Arrangement = ViewArrangement.Movable | ViewArrangement.Resizable + var view = new View + { + Arrangement = ViewArrangement.Movable | ViewArrangement.Resizable }; - + view.Arrangement &= ~ViewArrangement.Movable; Assert.False (view.Arrangement.HasFlag (ViewArrangement.Movable)); Assert.True (view.Arrangement.HasFlag (ViewArrangement.Resizable)); @@ -568,15 +568,15 @@ public class ArrangementTests (ITestOutputHelper output) [Fact] public void SuperView_CanHaveMultipleArrangeableSubViews () { - var superView = new View - { + var superView = new View + { Arrangement = ViewArrangement.Overlapped, Width = 80, Height = 25 }; - - var movableView = new View - { + + var movableView = new View + { Arrangement = ViewArrangement.Movable, BorderStyle = LineStyle.Single, X = 0, @@ -584,9 +584,9 @@ public class ArrangementTests (ITestOutputHelper output) Width = 20, Height = 10 }; - - var resizableView = new View - { + + var resizableView = new View + { Arrangement = ViewArrangement.Resizable, BorderStyle = LineStyle.Single, X = 25, @@ -594,9 +594,9 @@ public class ArrangementTests (ITestOutputHelper output) Width = 20, Height = 10 }; - - var fixedView = new View - { + + var fixedView = new View + { Arrangement = ViewArrangement.Fixed, BorderStyle = LineStyle.Single, X = 50, @@ -604,9 +604,9 @@ public class ArrangementTests (ITestOutputHelper output) Width = 20, Height = 10 }; - + superView.Add (movableView, resizableView, fixedView); - + Assert.Equal (3, superView.SubViews.Count); Assert.Equal (ViewArrangement.Movable, movableView.Arrangement); Assert.Equal (ViewArrangement.Resizable, resizableView.Arrangement); @@ -618,9 +618,9 @@ public class ArrangementTests (ITestOutputHelper output) { var superView = new View { Arrangement = ViewArrangement.Fixed }; var subView = new View { Arrangement = ViewArrangement.Movable }; - + superView.Add (subView); - + // SubView arrangement is independent of SuperView arrangement Assert.Equal (ViewArrangement.Fixed, superView.Arrangement); Assert.Equal (ViewArrangement.Movable, subView.Arrangement); @@ -633,30 +633,30 @@ public class ArrangementTests (ITestOutputHelper output) [Fact] public void Border_WithDefaultThickness_SupportsArrangement () { - var view = new View - { + var view = new View + { Arrangement = ViewArrangement.Movable, BorderStyle = LineStyle.Single }; - + Assert.NotNull (view.Border); // Default thickness should be (1,1,1,1) for Single line style - Assert.True (view.Border.Thickness.Left > 0 || view.Border.Thickness.Right > 0 + Assert.True (view.Border.Thickness.Left > 0 || view.Border.Thickness.Right > 0 || view.Border.Thickness.Top > 0 || view.Border.Thickness.Bottom > 0); } [Fact] public void Border_WithCustomThickness_SupportsArrangement () { - var view = new View - { + var view = new View + { Arrangement = ViewArrangement.LeftResizable, BorderStyle = LineStyle.Single }; - + // Set custom thickness - only left border view.Border!.Thickness = new Thickness (2, 0, 0, 0); - + Assert.Equal (2, view.Border.Thickness.Left); Assert.Equal (0, view.Border.Thickness.Top); Assert.Equal (0, view.Border.Thickness.Right); @@ -696,7 +696,7 @@ public class ArrangementTests (ITestOutputHelper output) // View.Navigation.cs checks Arrangement.HasFlag(ViewArrangement.Overlapped) var overlappedView = new View { Arrangement = ViewArrangement.Overlapped }; var tiledView = new View { Arrangement = ViewArrangement.Fixed }; - + Assert.True (overlappedView.Arrangement.HasFlag (ViewArrangement.Overlapped)); Assert.False (tiledView.Arrangement.HasFlag (ViewArrangement.Overlapped)); } @@ -708,9 +708,9 @@ public class ArrangementTests (ITestOutputHelper output) var parent = new View { Arrangement = ViewArrangement.Overlapped }; var child1 = new View { X = 0, Y = 0, Width = 10, Height = 10 }; var child2 = new View { X = 5, Y = 5, Width = 10, Height = 10 }; - + parent.Add (child1, child2); - + Assert.True (parent.Arrangement.HasFlag (ViewArrangement.Overlapped)); Assert.Equal (2, parent.SubViews.Count); } @@ -719,209 +719,7 @@ public class ArrangementTests (ITestOutputHelper output) #region Mouse Interaction Tests - [Fact] - public void MouseGrabHandler_WorksWithMovableView_UsingNewMouseEvent () - { - // This test proves that MouseGrabHandler works correctly with concurrent unit tests - // using NewMouseEvent directly on views, without requiring Application.Init - - var superView = new View - { - Width = 80, - Height = 25 - }; - - var movableView = new View - { - Arrangement = ViewArrangement.Movable, - BorderStyle = LineStyle.Single, - X = 10, - Y = 10, - Width = 20, - Height = 10 - }; - - superView.Add (movableView); - - // Verify initial state - Assert.NotNull (movableView.Border); - Assert.Null (Application.Mouse.MouseGrabView); - - // Simulate mouse press on the border to start dragging - var pressEvent = new MouseEventArgs - { - Position = new (1, 0), // Top border area - Flags = MouseFlags.Button1Pressed - }; - - bool? result = movableView.Border.NewMouseEvent (pressEvent); - - // The border should have grabbed the mouse - Assert.True (result); - Assert.Equal (movableView.Border, Application.Mouse.MouseGrabView); - - // Simulate mouse drag - var dragEvent = new MouseEventArgs - { - Position = new (5, 2), - Flags = MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition - }; - - result = movableView.Border.NewMouseEvent (dragEvent); - Assert.True (result); - - // Mouse should still be grabbed - Assert.Equal (movableView.Border, Application.Mouse.MouseGrabView); - - // Simulate mouse release to end dragging - var releaseEvent = new MouseEventArgs - { - Position = new (5, 2), - Flags = MouseFlags.Button1Released - }; - - result = movableView.Border.NewMouseEvent (releaseEvent); - Assert.True (result); - - // Mouse should be released - Assert.Null (Application.Mouse.MouseGrabView); - } - - [Fact] - public void MouseGrabHandler_WorksWithResizableView_UsingNewMouseEvent () - { - // This test proves MouseGrabHandler works for resizing operations - - var superView = new View - { - Width = 80, - Height = 25 - }; - - var resizableView = new View - { - Arrangement = ViewArrangement.RightResizable, - BorderStyle = LineStyle.Single, - X = 10, - Y = 10, - Width = 20, - Height = 10 - }; - - superView.Add (resizableView); - - // Verify initial state - Assert.NotNull (resizableView.Border); - Assert.Null (Application.Mouse.MouseGrabView); - - // Calculate position on right border (border is at right edge) - // Border.Frame.X is relative to parent, so we use coordinates relative to the border - var pressEvent = new MouseEventArgs - { - Position = new (resizableView.Border.Frame.Width - 1, 5), // Right border area - Flags = MouseFlags.Button1Pressed - }; - - bool? result = resizableView.Border.NewMouseEvent (pressEvent); - - // The border should have grabbed the mouse for resizing - Assert.True (result); - Assert.Equal (resizableView.Border, Application.Mouse.MouseGrabView); - - // Simulate dragging to resize - var dragEvent = new MouseEventArgs - { - Position = new (resizableView.Border.Frame.Width + 3, 5), - Flags = MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition - }; - - result = resizableView.Border.NewMouseEvent (dragEvent); - Assert.True (result); - Assert.Equal (resizableView.Border, Application.Mouse.MouseGrabView); - - // Simulate mouse release - var releaseEvent = new MouseEventArgs - { - Position = new (resizableView.Border.Frame.Width + 3, 5), - Flags = MouseFlags.Button1Released - }; - - result = resizableView.Border.NewMouseEvent (releaseEvent); - Assert.True (result); - - // Mouse should be released - Assert.Null (Application.Mouse.MouseGrabView); - } - - [Fact] - public void MouseGrabHandler_ReleasesOnMultipleViews () - { - // This test verifies MouseGrabHandler properly releases when switching between views - - var superView = new View { Width = 80, Height = 25 }; - - var view1 = new View - { - Arrangement = ViewArrangement.Movable, - BorderStyle = LineStyle.Single, - X = 10, - Y = 10, - Width = 15, - Height = 8 - }; - - var view2 = new View - { - Arrangement = ViewArrangement.Movable, - BorderStyle = LineStyle.Single, - X = 30, - Y = 10, - Width = 15, - Height = 8 - }; - - superView.Add (view1, view2); - - // Grab mouse on first view - var pressEvent1 = new MouseEventArgs - { - Position = new (1, 0), - Flags = MouseFlags.Button1Pressed - }; - - view1.Border!.NewMouseEvent (pressEvent1); - Assert.Equal (view1.Border, Application.Mouse.MouseGrabView); - - // Release on first view - var releaseEvent1 = new MouseEventArgs - { - Position = new (1, 0), - Flags = MouseFlags.Button1Released - }; - - view1.Border.NewMouseEvent (releaseEvent1); - Assert.Null (Application.Mouse.MouseGrabView); - - // Grab mouse on second view - var pressEvent2 = new MouseEventArgs - { - Position = new (1, 0), - Flags = MouseFlags.Button1Pressed - }; - - view2.Border!.NewMouseEvent (pressEvent2); - Assert.Equal (view2.Border, Application.Mouse.MouseGrabView); - - // Release on second view - var releaseEvent2 = new MouseEventArgs - { - Position = new (1, 0), - Flags = MouseFlags.Button1Released - }; - - view2.Border.NewMouseEvent (releaseEvent2); - Assert.Null (Application.Mouse.MouseGrabView); - } + // Not parallelizable due to Application.Mouse dependency in MouseGrabHandler #endregion @@ -954,13 +752,13 @@ public class ArrangementTests (ITestOutputHelper output) // This test verifies that all the arrangement tests in this file // can run without Application.Init, making them parallelizable. // If this test passes, it confirms no Application dependencies leaked in. - - var view = new View - { + + var view = new View + { Arrangement = ViewArrangement.Movable | ViewArrangement.Resizable, BorderStyle = LineStyle.Single }; - + Assert.NotNull (view); Assert.NotNull (view.Border); Assert.True (view.Arrangement.HasFlag (ViewArrangement.Movable)); @@ -974,9 +772,9 @@ public class ArrangementTests (ITestOutputHelper output) [Fact] public void ViewArrangement_CanCombineAllResizableDirections () { - ViewArrangement arrangement = ViewArrangement.TopResizable - | ViewArrangement.BottomResizable - | ViewArrangement.LeftResizable + ViewArrangement arrangement = ViewArrangement.TopResizable + | ViewArrangement.BottomResizable + | ViewArrangement.LeftResizable | ViewArrangement.RightResizable; Assert.True (arrangement.HasFlag (ViewArrangement.TopResizable)); @@ -1090,7 +888,7 @@ public class ArrangementTests (ITestOutputHelper output) public void View_MultipleSubviewsWithDifferentArrangements_EachIndependent () { var container = new View (); - + var fixedView = new View { Id = "fixed", Arrangement = ViewArrangement.Fixed }; var movableView = new View { Id = "movable", Arrangement = ViewArrangement.Movable }; var resizableView = new View { Id = "resizable", Arrangement = ViewArrangement.Resizable }; @@ -1112,7 +910,7 @@ public class ArrangementTests (ITestOutputHelper output) public void Overlapped_ViewCanBeMovedToFront () { var container = new View { Arrangement = ViewArrangement.Overlapped }; - + var view1 = new View { Id = "view1" }; var view2 = new View { Id = "view2" }; var view3 = new View { Id = "view3" }; @@ -1133,7 +931,7 @@ public class ArrangementTests (ITestOutputHelper output) public void Overlapped_ViewCanBeMovedToBack () { var container = new View { Arrangement = ViewArrangement.Overlapped }; - + var view1 = new View { Id = "view1" }; var view2 = new View { Id = "view2" }; var view3 = new View { Id = "view3" }; @@ -1142,12 +940,12 @@ public class ArrangementTests (ITestOutputHelper output) // Initial order: [view1, view2, view3] Assert.Equal ([view1, view2, view3], container.SubViews.ToArray ()); - + // Move view3 to end (top of Z-order) container.MoveSubViewToEnd (view3); Assert.Equal (view3, container.SubViews.ToArray () [^1]); Assert.Equal ([view1, view2, view3], container.SubViews.ToArray ()); - + // Now move view1 to end (making it on top, pushing view3 down) container.MoveSubViewToEnd (view1); Assert.Equal ([view2, view3, view1], container.SubViews.ToArray ()); @@ -1157,7 +955,7 @@ public class ArrangementTests (ITestOutputHelper output) public void Fixed_ViewAddOrderMattersForLayout () { var container = new View { Arrangement = ViewArrangement.Fixed }; - + var view1 = new View { Id = "view1", X = 0, Y = 0, Width = 10, Height = 5 }; var view2 = new View { Id = "view2", X = 5, Y = 2, Width = 10, Height = 5 }; diff --git a/Tests/UnitTestsParallelizable/View/Draw/NeedsDrawTests.cs b/Tests/UnitTestsParallelizable/View/Draw/NeedsDrawTests.cs index cf3b7a0c8..b8b3e7d43 100644 --- a/Tests/UnitTestsParallelizable/View/Draw/NeedsDrawTests.cs +++ b/Tests/UnitTestsParallelizable/View/Draw/NeedsDrawTests.cs @@ -1,8 +1,10 @@ #nullable enable +using UnitTests; + namespace UnitTests_Parallelizable.ViewTests; [Trait ("Category", "Output")] -public class NeedsDrawTests +public class NeedsDrawTests : FakeDriverBase { [Fact] public void NeedsDraw_False_If_Width_Height_Zero () @@ -18,7 +20,7 @@ public class NeedsDrawTests [Fact] public void NeedsDraw_True_Initially_If_Width_Height_Not_Zero () { - View superView = new () { Width = 1, Height = 1 }; + View superView = new () { Driver = CreateFakeDriver (), Width = 1, Height = 1 }; View view1 = new () { Width = 1, Height = 1 }; View view2 = new () { Width = 1, Height = 1 }; @@ -54,7 +56,7 @@ public class NeedsDrawTests var view = new View { Width = 2, Height = 2 }; Assert.True (view.NeedsDraw); - view = new() { Width = 2, Height = 2, BorderStyle = LineStyle.Single }; + view = new () { Width = 2, Height = 2, BorderStyle = LineStyle.Single }; Assert.True (view.NeedsDraw); } @@ -90,7 +92,7 @@ public class NeedsDrawTests view.EndInit (); Assert.True (view.NeedsDraw); - view = new() { Width = 2, Height = 2, BorderStyle = LineStyle.Single }; + view = new () { Width = 2, Height = 2, BorderStyle = LineStyle.Single }; view.BeginInit (); view.NeedsDraw = false; view.EndInit (); @@ -100,7 +102,7 @@ public class NeedsDrawTests [Fact] public void NeedsDraw_After_SetLayoutNeeded_And_Layout () { - var view = new View { Width = 2, Height = 2 }; + var view = new View { Driver = CreateFakeDriver (), Width = 2, Height = 2 }; Assert.True (view.NeedsDraw); Assert.False (view.NeedsLayout); @@ -120,7 +122,7 @@ public class NeedsDrawTests [Fact] public void NeedsDraw_False_After_SetRelativeLayout_Absolute_Dims () { - var view = new View { Width = 2, Height = 2 }; + var view = new View { Driver = CreateFakeDriver (), Width = 2, Height = 2 }; Assert.True (view.NeedsDraw); view.Draw (); @@ -224,7 +226,7 @@ public class NeedsDrawTests [Fact] public void NeedsDraw_False_After_Draw () { - var view = new View { Width = 2, Height = 2, BorderStyle = LineStyle.Single }; + var view = new View { Driver = CreateFakeDriver (), Width = 2, Height = 2, BorderStyle = LineStyle.Single }; Assert.True (view.NeedsDraw); view.BeginInit (); diff --git a/Tests/UnitTestsParallelizable/View/Draw/ViewClearViewportTests.cs b/Tests/UnitTestsParallelizable/View/Draw/ViewClearViewportTests.cs new file mode 100644 index 000000000..1ef96e203 --- /dev/null +++ b/Tests/UnitTestsParallelizable/View/Draw/ViewClearViewportTests.cs @@ -0,0 +1,371 @@ +using System.Text; +using UnitTests; +using Xunit.Abstractions; + +namespace UnitTests_Parallelizable.ViewTests; + +public class ViewClearViewportTests () : FakeDriverBase +{ + [Fact] + public void ClearViewport_FillsViewportArea () + { + IDriver driver = CreateFakeDriver (80, 25); + driver.Clip = new Region (driver.Screen); + + var view = new View + { + X = 1, + Y = 1, + Width = 20, + Height = 20, + Driver = driver + }; + view.BeginInit (); + view.EndInit (); + view.LayoutSubViews (); + + // Clear the driver contents first + driver.FillRect (driver.Screen, new Rune ('X')); + + view.ClearViewport (); + + // The viewport area should be filled with spaces + Rectangle viewportScreen = view.ViewportToScreen (view.Viewport with { Location = new (0, 0) }); + + for (int y = viewportScreen.Y; y < viewportScreen.Y + viewportScreen.Height; y++) + { + for (int x = viewportScreen.X; x < viewportScreen.X + viewportScreen.Width; x++) + { + Assert.Equal (new Rune (' '), driver.Contents [y, x].Rune); + } + } + } + + [Fact] + public void ClearViewport_WithClearContentOnly_LimitsToVisibleContent () + { + IDriver driver = CreateFakeDriver (80, 25); + driver.Clip = new Region (driver.Screen); + + var view = new View + { + X = 1, + Y = 1, + Width = 20, + Height = 20, + Driver = driver + }; + view.SetContentSize (new Size (100, 100)); // Content larger than viewport + view.ViewportSettings = ViewportSettingsFlags.ClearContentOnly; + view.BeginInit (); + view.EndInit (); + view.LayoutSubViews (); + + // Clear the driver contents first + driver.FillRect (driver.Screen, new Rune ('X')); + + view.ClearViewport (); + + // The visible content area should be cleared + Rectangle visibleContent = view.ViewportToScreen (new Rectangle (new (-view.Viewport.X, -view.Viewport.Y), view.GetContentSize ())); + Rectangle viewportScreen = view.ViewportToScreen (view.Viewport with { Location = new (0, 0) }); + Rectangle toClear = Rectangle.Intersect (viewportScreen, visibleContent); + + for (int y = toClear.Y; y < toClear.Y + toClear.Height; y++) + { + for (int x = toClear.X; x < toClear.X + toClear.Width; x++) + { + Assert.Equal (new Rune (' '), driver.Contents [y, x].Rune); + } + } + } + + [Fact] + public void ClearViewport_NullDriver_DoesNotThrow () + { + var view = new View + { + X = 1, + Y = 1, + Width = 20, + Height = 20 + }; + view.BeginInit (); + view.EndInit (); + var exception = Record.Exception (() => view.ClearViewport ()); + Assert.Null (exception); + } + + [Fact] + public void ClearViewport_SetsNeedsDraw () + { + IDriver driver = CreateFakeDriver (80, 25); + driver.Clip = new Region (driver.Screen); + + var view = new View + { + X = 10, + Y = 10, + Width = 20, + Height = 20, + Driver = driver + }; + view.BeginInit (); + view.EndInit (); + view.LayoutSubViews (); + + // Clear NeedsDraw first + view.Draw (); + Assert.False (view.NeedsDraw); + + view.ClearViewport (); + + Assert.True (view.NeedsDraw); + } + + [Fact] + public void ClearViewport_WithTransparentFlag_DoesNotClear () + { + IDriver driver = CreateFakeDriver (80, 25); + driver.Clip = new Region (driver.Screen); + + var view = new View + { + X = 1, + Y = 1, + Width = 20, + Height = 20, + Driver = driver, + ViewportSettings = ViewportSettingsFlags.Transparent + }; + view.BeginInit (); + view.EndInit (); + view.LayoutSubViews (); + + // Fill driver with a character + driver.FillRect (driver.Screen, new Rune ('X')); + + view.Draw (); + + // The viewport area should still have 'X' (not cleared) + Rectangle viewportScreen = view.ViewportToScreen (view.Viewport with { Location = new (0, 0) }); + + for (int y = viewportScreen.Y; y < viewportScreen.Y + viewportScreen.Height; y++) + { + for (int x = viewportScreen.X; x < viewportScreen.X + viewportScreen.Width; x++) + { + Assert.Equal (new Rune ('X'), driver.Contents [y, x].Rune); + } + } + } + + [Fact] + public void ClearingViewport_Event_Raised () + { + IDriver driver = CreateFakeDriver (80, 25); + driver.Clip = new Region (driver.Screen); + + bool eventRaised = false; + Rectangle? receivedRect = null; + + var view = new View + { + X = 10, + Y = 10, + Width = 20, + Height = 20, + Driver = driver + }; + view.BeginInit (); + view.EndInit (); + view.LayoutSubViews (); + + view.ClearingViewport += (s, e) => + { + eventRaised = true; + receivedRect = e.NewViewport; + }; + + view.Draw (); + + Assert.True (eventRaised); + Assert.NotNull (receivedRect); + Assert.Equal (view.Viewport, receivedRect); + } + + [Fact] + public void ClearedViewport_Event_Raised () + { + IDriver driver = CreateFakeDriver (80, 25); + driver.Clip = new Region (driver.Screen); + + bool eventRaised = false; + + var view = new View + { + X = 10, + Y = 10, + Width = 20, + Height = 20, + Driver = driver + }; + view.BeginInit (); + view.EndInit (); + view.LayoutSubViews (); + + view.ClearedViewport += (s, e) => eventRaised = true; + + view.Draw (); + + Assert.True (eventRaised); + } + + [Fact] + public void OnClearingViewport_CanPreventClear () + { + IDriver driver = CreateFakeDriver (80, 25); + driver.Clip = new Region (driver.Screen); + + bool clearedCalled = false; + + var view = new TestView + { + X = 10, + Y = 10, + Width = 20, + Height = 20, + Driver = driver, + PreventClear = true + }; + view.BeginInit (); + view.EndInit (); + view.LayoutSubViews (); + + view.ClearedViewport += (s, e) => clearedCalled = true; + + view.Draw (); + + Assert.False (clearedCalled); + } + + [Fact] + public void ClearViewport_EmptyViewport_DoesNotThrow () + { + IDriver driver = CreateFakeDriver (80, 25); + driver.Clip = new Region (driver.Screen); + + var view = new View + { + X = 10, + Y = 10, + Width = 1, + Height = 1, + Driver = driver + }; + view.Border!.Thickness = new Thickness (1); + view.BeginInit (); + view.EndInit (); + view.LayoutSubViews (); + + // With border of 1, viewport should be empty + Assert.True (view.Viewport.Width == 0 || view.Viewport.Height == 0); + + var exception = Record.Exception (() => view.ClearViewport ()); + + Assert.Null (exception); + } + + [Fact] + public void ClearViewport_WithScrolledViewport_ClearsCorrectArea () + { + IDriver driver = CreateFakeDriver (80, 25); + driver.Clip = new Region (driver.Screen); + + var view = new View + { + X = 1, + Y = 1, + Width = 20, + Height = 20, + Driver = driver + }; + view.SetContentSize (new Size (100, 100)); + view.BeginInit (); + view.EndInit (); + view.LayoutSubViews (); + + // Scroll the viewport + view.Viewport = view.Viewport with { X = 10, Y = 10 }; + + // Fill driver with a character + driver.FillRect (driver.Screen, new Rune ('X')); + + view.ClearViewport (); + + // The viewport area should be cleared (not the scrolled content area) + Rectangle viewportScreen = view.ViewportToScreen (view.Viewport with { Location = new (0, 0) }); + + for (int y = viewportScreen.Y; y < viewportScreen.Y + viewportScreen.Height; y++) + { + for (int x = viewportScreen.X; x < viewportScreen.X + viewportScreen.Width; x++) + { + Assert.Equal (new Rune (' '), driver.Contents [y, x].Rune); + } + } + } + + [Fact] + public void ClearViewport_WithClearContentOnly_AndScrolledViewport_ClearsOnlyVisibleContent () + { + IDriver driver = CreateFakeDriver (80, 25); + driver.Clip = new Region (driver.Screen); + + var view = new View + { + X = 10, + Y = 10, + Width = 20, + Height = 20, + Driver = driver + }; + view.SetContentSize (new Size (15, 15)); // Content smaller than viewport + view.ViewportSettings = ViewportSettingsFlags.ClearContentOnly; + view.BeginInit (); + view.EndInit (); + view.LayoutSubViews (); + + // Scroll past the content + view.Viewport = view.Viewport with { X = 5, Y = 5 }; + + // Fill driver with a character + driver.FillRect (driver.Screen, new Rune ('X')); + + view.ClearViewport (); + + // Only the visible part of the content should be cleared + Rectangle visibleContent = view.ViewportToScreen (new Rectangle (new (-view.Viewport.X, -view.Viewport.Y), view.GetContentSize ())); + Rectangle viewportScreen = view.ViewportToScreen (view.Viewport with { Location = new (0, 0) }); + Rectangle toClear = Rectangle.Intersect (viewportScreen, visibleContent); + + if (toClear != Rectangle.Empty) + { + for (int y = toClear.Y; y < toClear.Y + toClear.Height; y++) + { + for (int x = toClear.X; x < toClear.X + toClear.Width; x++) + { + Assert.Equal (new Rune (' '), driver.Contents [y, x].Rune); + } + } + } + } + + private class TestView : View + { + public bool PreventClear { get; set; } + + protected override bool OnClearingViewport () + { + return PreventClear || base.OnClearingViewport (); + } + } +} diff --git a/Tests/UnitTestsParallelizable/View/Draw/ViewDrawTextAndLineCanvasTests.cs b/Tests/UnitTestsParallelizable/View/Draw/ViewDrawTextAndLineCanvasTests.cs new file mode 100644 index 000000000..5f2af2055 --- /dev/null +++ b/Tests/UnitTestsParallelizable/View/Draw/ViewDrawTextAndLineCanvasTests.cs @@ -0,0 +1,495 @@ +using System.Text; +using UnitTests; +using Xunit.Abstractions; + +namespace UnitTests_Parallelizable.ViewTests; + +public class ViewDrawTextAndLineCanvasTests () : FakeDriverBase +{ + #region DrawText Tests + + [Fact] + public void DrawText_EmptyText_DoesNotThrow () + { + IDriver driver = CreateFakeDriver (80, 25); + driver.Clip = new Region (driver.Screen); + + var view = new View + { + X = 10, + Y = 10, + Width = 20, + Height = 20, + Driver = driver, + Text = "" + }; + view.BeginInit (); + view.EndInit (); + view.LayoutSubViews (); + + var exception = Record.Exception (() => view.Draw ()); + + Assert.Null (exception); + } + + [Fact] + public void DrawText_NullText_DoesNotThrow () + { + IDriver driver = CreateFakeDriver (80, 25); + driver.Clip = new Region (driver.Screen); + + var view = new View + { + X = 10, + Y = 10, + Width = 20, + Height = 20, + Driver = driver, + Text = null! + }; + view.BeginInit (); + view.EndInit (); + view.LayoutSubViews (); + + var exception = Record.Exception (() => view.Draw ()); + + Assert.Null (exception); + } + + [Fact] + public void DrawText_DrawsTextToDriver () + { + IDriver driver = CreateFakeDriver (80, 25); + driver.Clip = new Region (driver.Screen); + + var view = new View + { + X = 1, + Y = 1, + Width = 20, + Height = 20, + Driver = driver, + Text = "Test" + }; + view.BeginInit (); + view.EndInit (); + view.LayoutSubViews (); + + view.Draw (); + + // Text should appear at the content location + Point screenPos = view.ContentToScreen (Point.Empty); + + Assert.Equal ('T', (char)driver.Contents [screenPos.Y, screenPos.X].Rune.Value); + Assert.Equal ('e', (char)driver.Contents [screenPos.Y, screenPos.X + 1].Rune.Value); + Assert.Equal ('s', (char)driver.Contents [screenPos.Y, screenPos.X + 2].Rune.Value); + Assert.Equal ('t', (char)driver.Contents [screenPos.Y, screenPos.X + 3].Rune.Value); + } + + [Fact] + public void DrawText_WithFocus_UsesFocusAttribute () + { + IDriver driver = CreateFakeDriver (80, 25); + driver.Clip = new Region (driver.Screen); + + var view = new View + { + X = 10, + Y = 10, + Width = 20, + Height = 20, + Driver = driver, + Text = "Test", + CanFocus = true + }; + view.BeginInit (); + view.EndInit (); + view.LayoutSubViews (); + view.SetFocus (); + + view.Draw (); + + // Text should use focus attribute + Point screenPos = view.ContentToScreen (Point.Empty); + Attribute expectedAttr = view.GetAttributeForRole (VisualRole.Focus); + + Assert.Equal (expectedAttr, driver.Contents [screenPos.Y, screenPos.X].Attribute); + } + + [Fact] + public void DrawText_WithoutFocus_UsesNormalAttribute () + { + IDriver driver = CreateFakeDriver (80, 25); + driver.Clip = new Region (driver.Screen); + + var view = new View + { + X = 10, + Y = 10, + Width = 20, + Height = 20, + Driver = driver, + Text = "Test", + CanFocus = true + }; + view.BeginInit (); + view.EndInit (); + view.LayoutSubViews (); + + view.Draw (); + + // Text should use normal attribute + Point screenPos = view.ContentToScreen (Point.Empty); + Attribute expectedAttr = view.GetAttributeForRole (VisualRole.Normal); + + Assert.Equal (expectedAttr, driver.Contents [screenPos.Y, screenPos.X].Attribute); + } + + [Fact] + public void DrawText_SetsSubViewNeedsDraw () + { + IDriver driver = CreateFakeDriver (80, 25); + driver.Clip = new Region (driver.Screen); + + var view = new View + { + X = 10, + Y = 10, + Width = 20, + Height = 20, + Driver = driver, + Text = "Test" + }; + var child = new View { X = 0, Y = 0, Width = 10, Height = 10 }; + view.Add (child); + view.BeginInit (); + view.EndInit (); + view.LayoutSubViews (); + + // Clear SubViewNeedsDraw + view.Draw (); + Assert.False (view.SubViewNeedsDraw); + + // Call DrawText directly which should set SubViewNeedsDraw + view.DrawText (); + + // SubViews need to be redrawn since text was drawn over them + Assert.True (view.SubViewNeedsDraw); + } + + [Fact] + public void DrawingText_Event_Raised () + { + IDriver driver = CreateFakeDriver (80, 25); + driver.Clip = new Region (driver.Screen); + + bool eventRaised = false; + + var view = new View + { + X = 10, + Y = 10, + Width = 20, + Height = 20, + Driver = driver, + Text = "Test" + }; + view.BeginInit (); + view.EndInit (); + view.LayoutSubViews (); + + view.DrawingText += (s, e) => eventRaised = true; + + view.Draw (); + + Assert.True (eventRaised); + } + + [Fact] + public void DrewText_Event_Raised () + { + IDriver driver = CreateFakeDriver (80, 25); + driver.Clip = new Region (driver.Screen); + + bool eventRaised = false; + + var view = new View + { + X = 10, + Y = 10, + Width = 20, + Height = 20, + Driver = driver, + Text = "Test" + }; + view.BeginInit (); + view.EndInit (); + view.LayoutSubViews (); + + view.DrewText += (s, e) => eventRaised = true; + + view.Draw (); + + Assert.True (eventRaised); + } + + #endregion + + #region LineCanvas Tests + + [Fact] + public void LineCanvas_InitiallyEmpty () + { + var view = new View (); + + Assert.NotNull (view.LineCanvas); + Assert.Equal (Rectangle.Empty, view.LineCanvas.Bounds); + } + + [Fact] + public void RenderLineCanvas_DrawsLines () + { + IDriver driver = CreateFakeDriver (80, 25); + driver.Clip = new Region (driver.Screen); + + var view = new View + { + X = 10, + Y = 10, + Width = 20, + Height = 20, + Driver = driver + }; + view.BeginInit (); + view.EndInit (); + view.LayoutSubViews (); + + // Add a line to the canvas + Point screenPos = new Point (15, 15); + view.LineCanvas.AddLine (screenPos, 5, Orientation.Horizontal, LineStyle.Single); + + view.RenderLineCanvas (); + + // Verify the line was drawn (check for horizontal line character) + for (int i = 0; i < 5; i++) + { + Assert.NotEqual (new Rune (' '), driver.Contents [screenPos.Y, screenPos.X + i].Rune); + } + } + + [Fact] + public void RenderLineCanvas_ClearsAfterRendering () + { + IDriver driver = CreateFakeDriver (80, 25); + driver.Clip = new Region (driver.Screen); + + var view = new View + { + X = 10, + Y = 10, + Width = 20, + Height = 20, + Driver = driver + }; + view.BeginInit (); + view.EndInit (); + view.LayoutSubViews (); + + // Add a line to the canvas + view.LineCanvas.AddLine (new Point (15, 15), 5, Orientation.Horizontal, LineStyle.Single); + + Assert.NotEqual (Rectangle.Empty, view.LineCanvas.Bounds); + + view.RenderLineCanvas (); + + // LineCanvas should be cleared after rendering + Assert.Equal (Rectangle.Empty, view.LineCanvas.Bounds); + } + + [Fact] + public void RenderLineCanvas_WithSuperViewRendersLineCanvas_DoesNotClear () + { + IDriver driver = CreateFakeDriver (80, 25); + driver.Clip = new Region (driver.Screen); + + var view = new View + { + X = 10, + Y = 10, + Width = 20, + Height = 20, + Driver = driver, + SuperViewRendersLineCanvas = true + }; + view.BeginInit (); + view.EndInit (); + view.LayoutSubViews (); + + // Add a line to the canvas + view.LineCanvas.AddLine (new Point (15, 15), 5, Orientation.Horizontal, LineStyle.Single); + + Rectangle boundsBefore = view.LineCanvas.Bounds; + + view.RenderLineCanvas (); + + // LineCanvas should NOT be cleared when SuperViewRendersLineCanvas is true + Assert.Equal (boundsBefore, view.LineCanvas.Bounds); + } + + [Fact] + public void SuperViewRendersLineCanvas_MergesWithParentCanvas () + { + IDriver driver = CreateFakeDriver (80, 25); + driver.Clip = new Region (driver.Screen); + + var parent = new View + { + X = 10, + Y = 10, + Width = 50, + Height = 50, + Driver = driver + }; + var child = new View + { + X = 5, + Y = 5, + Width = 30, + Height = 30, + SuperViewRendersLineCanvas = true + }; + parent.Add (child); + parent.BeginInit (); + parent.EndInit (); + parent.LayoutSubViews (); + + // Add a line to child's canvas + child.LineCanvas.AddLine (new Point (20, 20), 5, Orientation.Horizontal, LineStyle.Single); + + Assert.NotEqual (Rectangle.Empty, child.LineCanvas.Bounds); + Assert.Equal (Rectangle.Empty, parent.LineCanvas.Bounds); + + parent.Draw (); + + // Child's canvas should have been merged into parent's + // and child's canvas should be cleared + Assert.Equal (Rectangle.Empty, child.LineCanvas.Bounds); + } + + [Fact] + public void OnRenderingLineCanvas_CanPreventRendering () + { + IDriver driver = CreateFakeDriver (80, 25); + driver.Clip = new Region (driver.Screen); + + var view = new TestView + { + X = 10, + Y = 10, + Width = 20, + Height = 20, + Driver = driver, + PreventRenderLineCanvas = true + }; + view.BeginInit (); + view.EndInit (); + view.LayoutSubViews (); + + // Add a line to the canvas + Point screenPos = new Point (15, 15); + view.LineCanvas.AddLine (screenPos, 5, Orientation.Horizontal, LineStyle.Single); + + view.Draw (); + + // When OnRenderingLineCanvas returns true, RenderLineCanvas is not called + // So the LineCanvas should still have lines (not cleared) + // BUT because SuperViewRendersLineCanvas is false (default), the LineCanvas + // gets cleared during the draw cycle anyway. We need to check that the + // line was NOT actually rendered to the driver. + bool lineRendered = true; + for (int i = 0; i < 5; i++) + { + if (driver.Contents [screenPos.Y, screenPos.X + i].Rune.Value == ' ') + { + lineRendered = false; + break; + } + } + + Assert.False (lineRendered); + } + + #endregion + + #region SuperViewRendersLineCanvas Tests + + [Fact] + public void SuperViewRendersLineCanvas_DefaultFalse () + { + var view = new View (); + + Assert.False (view.SuperViewRendersLineCanvas); + } + + [Fact] + public void SuperViewRendersLineCanvas_CanBeSet () + { + var view = new View { SuperViewRendersLineCanvas = true }; + + Assert.True (view.SuperViewRendersLineCanvas); + } + + [Fact] + public void Draw_WithSuperViewRendersLineCanvas_SetsNeedsDraw () + { + IDriver driver = CreateFakeDriver (80, 25); + driver.Clip = new Region (driver.Screen); + + var parent = new View + { + X = 10, + Y = 10, + Width = 50, + Height = 50, + Driver = driver + }; + var child = new View + { + X = 5, + Y = 5, + Width = 30, + Height = 30, + SuperViewRendersLineCanvas = true + }; + parent.Add (child); + parent.BeginInit (); + parent.EndInit (); + parent.LayoutSubViews (); + + // Draw once to clear NeedsDraw + parent.Draw (); + Assert.False (child.NeedsDraw); + + // Draw again - child with SuperViewRendersLineCanvas should be redrawn + parent.Draw (); + + // The child should have been set to NeedsDraw during DrawSubViews + // This is verified by the fact that it was drawn (we can't check NeedsDraw after Draw) + } + + #endregion + + #region Helper Test View + + private class TestView : View + { + public bool PreventRenderLineCanvas { get; set; } + + protected override bool OnRenderingLineCanvas () + { + return PreventRenderLineCanvas || base.OnRenderingLineCanvas (); + } + } + + #endregion +} diff --git a/Tests/UnitTestsParallelizable/View/Draw/ViewDrawingClippingTests.cs b/Tests/UnitTestsParallelizable/View/Draw/ViewDrawingClippingTests.cs new file mode 100644 index 000000000..ba5941efc --- /dev/null +++ b/Tests/UnitTestsParallelizable/View/Draw/ViewDrawingClippingTests.cs @@ -0,0 +1,754 @@ +#nullable enable +using UnitTests; +using Xunit.Abstractions; + +namespace UnitTests_Parallelizable.ViewTests; + +public class ViewDrawingClippingTests () : FakeDriverBase +{ + #region GetClip / SetClip Tests + + + [Fact] + public void GetClip_ReturnsDriverClip () + { + IDriver driver = CreateFakeDriver (80, 25); + var region = new Region (new Rectangle (10, 10, 20, 20)); + driver.Clip = region; + View view = new () { Driver = driver }; + + Region? result = view.GetClip (); + + Assert.NotNull (result); + Assert.Equal (region, result); + } + + [Fact] + public void SetClip_NullRegion_DoesNothing () + { + IDriver driver = CreateFakeDriver (80, 25); + var original = new Region (new Rectangle (5, 5, 10, 10)); + driver.Clip = original; + + View view = new () { Driver = driver }; + + view.SetClip (null); + + Assert.Equal (original, driver.Clip); + } + + [Fact] + public void SetClip_ValidRegion_SetsDriverClip () + { + IDriver driver = CreateFakeDriver (80, 25); + var region = new Region (new Rectangle (10, 10, 30, 30)); + View view = new () { Driver = driver }; + + view.SetClip (region); + + Assert.Equal (region, driver.Clip); + } + + #endregion + + #region SetClipToScreen Tests + + [Fact] + public void SetClipToScreen_ReturnsPreviousClip () + { + IDriver driver = CreateFakeDriver (80, 25); + var original = new Region (new Rectangle (5, 5, 10, 10)); + driver.Clip = original; + View view = new () { Driver = driver }; + + Region? previous = view.SetClipToScreen (); + + Assert.Equal (original, previous); + Assert.NotEqual (original, driver.Clip); + + Application.ResetState (true); + } + + [Fact] + public void SetClipToScreen_SetsClipToScreen () + { + IDriver driver = CreateFakeDriver (80, 25); + Application.Driver = driver; + View view = new () { Driver = driver }; + + view.SetClipToScreen (); + + Assert.NotNull (driver.Clip); + Assert.Equal (driver.Screen, driver.Clip.GetBounds ()); + + Application.ResetState (true); + } + + #endregion + + #region ExcludeFromClip Tests + + [Fact] + public void ExcludeFromClip_Rectangle_NullDriver_DoesNotThrow () + { + View view = new () { Driver = null }; + var exception = Record.Exception (() => view.ExcludeFromClip (new Rectangle (5, 5, 10, 10))); + Assert.Null (exception); + + Application.ResetState (true); + } + + [Fact] + public void ExcludeFromClip_Rectangle_ExcludesArea () + { + IDriver driver = CreateFakeDriver (80, 25); + driver.Clip = new Region (new Rectangle (0, 0, 80, 25)); + Application.Driver = driver; + View view = new () { Driver = driver }; + + var toExclude = new Rectangle (10, 10, 20, 20); + view.ExcludeFromClip (toExclude); + + // Verify the region was excluded + Assert.NotNull (driver.Clip); + Assert.False (driver.Clip.Contains (15, 15)); + + Application.ResetState (true); + } + + [Fact] + public void ExcludeFromClip_Region_NullDriver_DoesNotThrow () + { + View view = new () { Driver = null }; + + var exception = Record.Exception (() => view.ExcludeFromClip (new Region (new Rectangle (5, 5, 10, 10)))); + Assert.Null (exception); + + Application.ResetState (true); + } + + [Fact] + public void ExcludeFromClip_Region_ExcludesArea () + { + IDriver driver = CreateFakeDriver (80, 25); + driver.Clip = new Region (new Rectangle (0, 0, 80, 25)); + View view = new () { Driver = driver }; + + + var toExclude = new Region (new Rectangle (10, 10, 20, 20)); + view.ExcludeFromClip (toExclude); + + // Verify the region was excluded + Assert.NotNull (driver.Clip); + Assert.False (driver.Clip.Contains (15, 15)); + + Application.ResetState (true); + } + + #endregion + + #region AddFrameToClip Tests + + [Fact] + public void AddFrameToClip_NullDriver_ReturnsNull () + { + var view = new View { X = 0, Y = 0, Width = 10, Height = 10 }; + view.BeginInit (); + view.EndInit (); + + Region? result = view.AddFrameToClip (); + + Assert.Null (result); + } + + [Fact] + public void AddFrameToClip_IntersectsWithFrame () + { + IDriver driver = CreateFakeDriver (80, 25); + driver.Clip = new Region (driver.Screen); + + var view = new View + { + X = 1, + Y = 1, + Width = 20, + Height = 20, + Driver = driver + }; + view.BeginInit (); + view.EndInit (); + view.LayoutSubViews (); + + Region? previous = view.AddFrameToClip (); + + Assert.NotNull (previous); + Assert.NotNull (driver.Clip); + + // The clip should now be the intersection of the screen and the view's frame + Rectangle expectedBounds = new Rectangle (1, 1, 20, 20); + Assert.Equal (expectedBounds, driver.Clip.GetBounds ()); + } + + #endregion + + #region AddViewportToClip Tests + + [Fact] + public void AddViewportToClip_NullDriver_ReturnsNull () + { + var view = new View { X = 0, Y = 0, Width = 10, Height = 10 }; + view.BeginInit (); + view.EndInit (); + + Region? result = view.AddViewportToClip (); + + Assert.Null (result); + } + + [Fact] + public void AddViewportToClip_IntersectsWithViewport () + { + IDriver driver = CreateFakeDriver (80, 25); + driver.Clip = new Region (driver.Screen); + + var view = new View + { + X = 1, + Y = 1, + Width = 20, + Height = 20, + Driver = driver + }; + view.BeginInit (); + view.EndInit (); + view.LayoutSubViews (); + + Region? previous = view.AddViewportToClip (); + + Assert.NotNull (previous); + Assert.NotNull (driver.Clip); + + // The clip should be the viewport area + Rectangle viewportScreen = view.ViewportToScreen (new Rectangle (Point.Empty, view.Viewport.Size)); + Assert.Equal (viewportScreen, driver.Clip.GetBounds ()); + } + + [Fact] + public void AddViewportToClip_WithClipContentOnly_LimitsToVisibleContent () + { + IDriver driver = CreateFakeDriver (80, 25); + driver.Clip = new Region (driver.Screen); + + var view = new View + { + X = 1, + Y = 1, + Width = 20, + Height = 20, + Driver = driver + }; + view.SetContentSize (new Size (100, 100)); + view.ViewportSettings = ViewportSettingsFlags.ClipContentOnly; + view.BeginInit (); + view.EndInit (); + view.LayoutSubViews (); + + Region? previous = view.AddViewportToClip (); + + Assert.NotNull (previous); + Assert.NotNull (driver.Clip); + + // The clip should be limited to visible content + Rectangle visibleContent = view.ViewportToScreen (new Rectangle (new (-view.Viewport.X, -view.Viewport.Y), view.GetContentSize ())); + Rectangle viewport = view.ViewportToScreen (new Rectangle (Point.Empty, view.Viewport.Size)); + Rectangle expected = Rectangle.Intersect (viewport, visibleContent); + + Assert.Equal (expected, driver.Clip.GetBounds ()); + } + + #endregion + + #region Clip Interaction Tests + + [Fact] + public void ClipRegions_StackCorrectly_WithNestedViews () + { + IDriver driver = CreateFakeDriver (100, 100); + driver.Clip = new Region (driver.Screen); + + var superView = new View + { + X = 1, + Y = 1, + Width = 50, + Height = 50, + Driver = driver + }; + superView.BeginInit (); + superView.EndInit (); + + var view = new View + { + X = 5, + Y = 5, + Width = 30, + Height = 30, + }; + superView.Add (view); + superView.LayoutSubViews (); + + // Set clip to superView's frame + Region? superViewClip = superView.AddFrameToClip (); + Rectangle superViewBounds = driver.Clip.GetBounds (); + + // Now set clip to view's frame + Region? viewClip = view.AddFrameToClip (); + Rectangle viewBounds = driver.Clip.GetBounds (); + + // Child clip should be within superView clip + Assert.True (superViewBounds.Contains (viewBounds.Location)); + + // Restore superView clip + view.SetClip (superViewClip); + // Assert.Equal (superViewBounds, driver.Clip.GetBounds ()); + } + + [Fact] + public void ClipRegions_RespectPreviousClip () + { + IDriver driver = CreateFakeDriver (80, 25); + var initialClip = new Region (new Rectangle (20, 20, 40, 40)); + driver.Clip = initialClip; + + var view = new View + { + X = 1, + Y = 1, + Width = 60, + Height = 60, + Driver = driver + }; + view.BeginInit (); + view.EndInit (); + view.LayoutSubViews (); + + Region? previous = view.AddFrameToClip (); + + // The new clip should be the intersection of the initial clip and the view's frame + Rectangle expected = Rectangle.Intersect ( + initialClip.GetBounds (), + view.FrameToScreen () + ); + + Assert.Equal (expected, driver.Clip.GetBounds ()); + + // Restore should give us back the original + view.SetClip (previous); + Assert.Equal (initialClip.GetBounds (), driver.Clip.GetBounds ()); + } + + #endregion + + #region Edge Cases + + [Fact] + public void AddFrameToClip_EmptyFrame_WorksCorrectly () + { + IDriver driver = CreateFakeDriver (80, 25); + driver.Clip = new Region (driver.Screen); + + var view = new View + { + X = 1, + Y = 1, + Width = 0, + Height = 0, + Driver = driver + }; + view.BeginInit (); + view.EndInit (); + view.LayoutSubViews (); + + Region? previous = view.AddFrameToClip (); + + Assert.NotNull (previous); + Assert.NotNull (driver.Clip); + } + + [Fact] + public void AddViewportToClip_EmptyViewport_WorksCorrectly () + { + IDriver driver = CreateFakeDriver (80, 25); + driver.Clip = new Region (driver.Screen); + + var view = new View + { + X = 1, + Y = 1, + Width = 1, // Minimal size to have adornments + Height = 1, + Driver = driver + }; + view.Border!.Thickness = new Thickness (1); + view.BeginInit (); + view.EndInit (); + view.LayoutSubViews (); + + // With border thickness of 1, the viewport should be empty + Assert.True (view.Viewport.Size.Width == 0 || view.Viewport.Size.Height == 0); + + Region? previous = view.AddViewportToClip (); + + Assert.NotNull (previous); + } + + [Fact] + public void ClipRegions_OutOfBounds_HandledCorrectly () + { + IDriver driver = CreateFakeDriver (80, 25); + driver.Clip = new Region (driver.Screen); + + var view = new View + { + X = 100, // Outside screen bounds + Y = 100, + Width = 20, + Height = 20, + Driver = driver + }; + view.BeginInit (); + view.EndInit (); + view.LayoutSubViews (); + + Region? previous = view.AddFrameToClip (); + + Assert.NotNull (previous); + // The clip should be empty since the view is outside the screen + Assert.True (driver.Clip.IsEmpty () || !driver.Clip.Contains (100, 100)); + } + + #endregion + + #region Drawing Tests + + [Fact] + public void Clip_Set_BeforeDraw_ClipsDrawing () + { + IDriver driver = CreateFakeDriver (80, 25); + var clip = new Region (new Rectangle (10, 10, 10, 10)); + driver.Clip = clip; + + var view = new View + { + X = 0, + Y = 0, + Width = 50, + Height = 50, + Driver = driver + }; + view.BeginInit (); + view.EndInit (); + view.LayoutSubViews (); + + view.Draw (); + + // Verify clip was used + Assert.NotNull (driver.Clip); + } + + [Fact] + public void Draw_UpdatesDriverClip () + { + IDriver driver = CreateFakeDriver (80, 25); + driver.Clip = new Region (driver.Screen); + + var view = new View + { + X = 1, + Y = 1, + Width = 20, + Height = 20, + Driver = driver + }; + view.BeginInit (); + view.EndInit (); + view.LayoutSubViews (); + + view.Draw (); + + // Clip should be updated to exclude the drawn view + Assert.NotNull (driver.Clip); + // Assert.False (driver.Clip.Contains (15, 15)); // Point inside the view should be excluded + } + + [Fact] + public void Draw_WithSubViews_ClipsCorrectly () + { + IDriver driver = CreateFakeDriver (80, 25); + driver.Clip = new Region (driver.Screen); + + var superView = new View + { + X = 1, + Y = 1, + Width = 50, + Height = 50, + Driver = driver + }; + var view = new View { X = 5, Y = 5, Width = 20, Height = 20 }; + superView.Add (view); + superView.BeginInit (); + superView.EndInit (); + superView.LayoutSubViews (); + + superView.Draw (); + + // Both superView and view should be excluded from clip + Assert.NotNull (driver.Clip); + // Assert.False (driver.Clip.Contains (15, 15)); // Point in superView should be excluded + } + + [Fact] + public void Draw_NonVisibleView_DoesNotUpdateClip () + { + IDriver driver = CreateFakeDriver (80, 25); + var originalClip = new Region (driver.Screen); + driver.Clip = originalClip.Clone (); + + var view = new View + { + X = 10, + Y = 10, + Width = 20, + Height = 20, + Visible = false, + Driver = driver + }; + view.BeginInit (); + view.EndInit (); + + view.Draw (); + + // Clip should not be modified for invisible views + Assert.True (driver.Clip.Equals (originalClip)); + } + + [Fact] + public void ExcludeFromClip_ExcludesRegion () + { + IDriver driver = CreateFakeDriver (80, 25); + driver.Clip = new Region (driver.Screen); + Application.Driver = driver; + + var view = new View + { + X = 10, + Y = 10, + Width = 20, + Height = 20, + Driver = driver + }; + view.BeginInit (); + view.EndInit (); + view.LayoutSubViews (); + + var excludeRect = new Rectangle (15, 15, 10, 10); + view.ExcludeFromClip (excludeRect); + + Assert.NotNull (driver.Clip); + Assert.False (driver.Clip.Contains (20, 20)); // Point inside excluded rect should not be in clip + + Application.ResetState (true); + } + + [Fact] + public void ExcludeFromClip_WithNullClip_DoesNotThrow () + { + IDriver driver = CreateFakeDriver (80, 25); + driver.Clip = null!; + Application.Driver = driver; + + var view = new View + { + X = 10, + Y = 10, + Width = 20, + Height = 20, + Driver = driver + }; + + var exception = Record.Exception (() => view.ExcludeFromClip (new Rectangle (15, 15, 10, 10))); + + Assert.Null (exception); + + Application.ResetState (true); + } + + #endregion + + #region Misc Tests + + [Fact] + public void SetClip_SetsDriverClip () + { + IDriver driver = CreateFakeDriver (80, 25); + + var view = new View + { + X = 10, + Y = 10, + Width = 20, + Height = 20, + Driver = driver + }; + + var newClip = new Region (new Rectangle (5, 5, 30, 30)); + view.SetClip (newClip); + + Assert.Equal (newClip, driver.Clip); + } + + [Fact (Skip = "See BUGBUG in SetClip")] + public void SetClip_WithNullClip_ClearsClip () + { + IDriver driver = CreateFakeDriver (80, 25); + driver.Clip = new Region (new Rectangle (10, 10, 20, 20)); + + var view = new View + { + X = 10, + Y = 10, + Width = 20, + Height = 20, + Driver = driver + }; + + view.SetClip (null); + + Assert.Null (driver.Clip); + } + + [Fact] + public void Draw_Excludes_View_From_Clip () + { + IDriver driver = CreateFakeDriver (80, 25); + var originalClip = new Region (driver.Screen); + driver.Clip = originalClip.Clone (); + + var view = new View + { + X = 10, + Y = 10, + Width = 20, + Height = 20, + Driver = driver + }; + view.BeginInit (); + view.EndInit (); + view.LayoutSubViews (); + + Region clipWithViewExcluded = originalClip.Clone (); + clipWithViewExcluded.Exclude (view.Frame); + + view.Draw (); + + Assert.Equal (clipWithViewExcluded, driver.Clip); + Assert.NotNull (driver.Clip); + } + + [Fact] + public void Draw_EmptyViewport_DoesNotCrash () + { + IDriver driver = CreateFakeDriver (80, 25); + driver.Clip = new Region (driver.Screen); + + var view = new View + { + X = 10, + Y = 10, + Width = 1, + Height = 1, + Driver = driver + }; + view.Border!.Thickness = new Thickness (1); + view.BeginInit (); + view.EndInit (); + view.LayoutSubViews (); + + // With border of 1, viewport should be empty (0x0 or negative) + var exception = Record.Exception (() => view.Draw ()); + + Assert.Null (exception); + } + + [Fact] + public void Draw_VeryLargeView_HandlesClippingCorrectly () + { + IDriver driver = CreateFakeDriver (80, 25); + driver.Clip = new Region (driver.Screen); + + var view = new View + { + X = 0, + Y = 0, + Width = 1000, + Height = 1000, + Driver = driver + }; + view.BeginInit (); + view.EndInit (); + view.LayoutSubViews (); + + var exception = Record.Exception (() => view.Draw ()); + + Assert.Null (exception); + } + + [Fact] + public void Draw_NegativeCoordinates_HandlesClippingCorrectly () + { + IDriver driver = CreateFakeDriver (80, 25); + driver.Clip = new Region (driver.Screen); + + var view = new View + { + X = -10, + Y = -10, + Width = 50, + Height = 50, + Driver = driver + }; + view.BeginInit (); + view.EndInit (); + view.LayoutSubViews (); + + var exception = Record.Exception (() => view.Draw ()); + + Assert.Null (exception); + } + + [Fact] + public void Draw_OutOfScreenBounds_HandlesClippingCorrectly () + { + IDriver driver = CreateFakeDriver (80, 25); + driver.Clip = new Region (driver.Screen); + + var view = new View + { + X = 100, + Y = 100, + Width = 50, + Height = 50, + Driver = driver + }; + view.BeginInit (); + view.EndInit (); + view.LayoutSubViews (); + + var exception = Record.Exception (() => view.Draw ()); + + Assert.Null (exception); + } + + #endregion +} diff --git a/Tests/UnitTestsParallelizable/View/Draw/ViewDrawingFlowTests.cs b/Tests/UnitTestsParallelizable/View/Draw/ViewDrawingFlowTests.cs new file mode 100644 index 000000000..58f2bbb18 --- /dev/null +++ b/Tests/UnitTestsParallelizable/View/Draw/ViewDrawingFlowTests.cs @@ -0,0 +1,701 @@ +#nullable enable +using UnitTests; +using Xunit.Abstractions; + +namespace UnitTests_Parallelizable.ViewTests; + +public class ViewDrawingFlowTests () : FakeDriverBase +{ + #region NeedsDraw Tests + + [Fact] + public void NeedsDraw_InitiallyFalse_WhenNotVisible () + { + var view = new View { Visible = false }; + view.BeginInit (); + view.EndInit (); + + Assert.False (view.NeedsDraw); + } + + [Fact] + public void NeedsDraw_TrueAfterSetNeedsDraw () + { + var view = new View { X = 0, Y = 0, Width = 10, Height = 10 }; + view.BeginInit (); + view.EndInit (); + view.LayoutSubViews (); + + view.SetNeedsDraw (); + + Assert.True (view.NeedsDraw); + } + + [Fact] + public void NeedsDraw_ClearedAfterDraw () + { + IDriver driver = CreateFakeDriver (80, 25); + driver.Clip = new Region (driver.Screen); + + var view = new View + { + X = 0, + Y = 0, + Width = 10, + Height = 10, + Driver = driver + }; + view.BeginInit (); + view.EndInit (); + view.LayoutSubViews (); + + view.SetNeedsDraw (); + Assert.True (view.NeedsDraw); + + view.Draw (); + + Assert.False (view.NeedsDraw); + } + + [Fact] + public void SetNeedsDraw_WithRectangle_UpdatesNeedsDrawRect () + { + var view = new View { Driver = CreateFakeDriver (), X = 0, Y = 0, Width = 20, Height = 20 }; + view.BeginInit (); + view.EndInit (); + view.LayoutSubViews (); + + // After layout, view will have NeedsDrawRect set to the viewport + // We need to clear it first + view.Draw (); + Assert.False (view.NeedsDraw); + Assert.Equal (Rectangle.Empty, view.NeedsDrawRect); + + var rect = new Rectangle (5, 5, 10, 10); + view.SetNeedsDraw (rect); + + Assert.True (view.NeedsDraw); + Assert.Equal (rect, view.NeedsDrawRect); + } + + [Fact] + public void SetNeedsDraw_MultipleRectangles_Expands () + { + IDriver driver = CreateFakeDriver (80, 25); + driver.Clip = new Region (driver.Screen); + + var view = new View { X = 0, Y = 0, Width = 30, Height = 30, Driver = driver }; + view.BeginInit (); + view.EndInit (); + view.LayoutSubViews (); + + // After layout, clear NeedsDraw + view.Draw (); + Assert.False (view.NeedsDraw); + + view.SetNeedsDraw (new Rectangle (5, 5, 10, 10)); + view.SetNeedsDraw (new Rectangle (15, 15, 10, 10)); + + // Should expand to cover the entire viewport when we have overlapping regions + // The current implementation expands to viewport size + Rectangle expected = new Rectangle (0, 0, 30, 30); + Assert.Equal (expected, view.NeedsDrawRect); + } + + [Fact] + public void SetNeedsDraw_NotVisible_DoesNotSet () + { + var view = new View + { + X = 0, + Y = 0, + Width = 10, + Height = 10, + Visible = false + }; + view.BeginInit (); + view.EndInit (); + + view.SetNeedsDraw (); + + Assert.False (view.NeedsDraw); + } + + [Fact] + public void SetNeedsDraw_PropagatesToSuperView () + { + var parent = new View { X = 0, Y = 0, Width = 50, Height = 50 }; + var child = new View { X = 10, Y = 10, Width = 20, Height = 20 }; + parent.Add (child); + parent.BeginInit (); + parent.EndInit (); + parent.LayoutSubViews (); + + child.SetNeedsDraw (); + + Assert.True (child.NeedsDraw); + Assert.True (parent.SubViewNeedsDraw); + } + + [Fact] + public void SetNeedsDraw_SetsAdornmentsNeedsDraw () + { + var view = new View { X = 0, Y = 0, Width = 20, Height = 20 }; + view.Border!.Thickness = new Thickness (1); + view.Padding!.Thickness = new Thickness (1); + view.BeginInit (); + view.EndInit (); + view.LayoutSubViews (); + + view.SetNeedsDraw (); + + Assert.True (view.Border!.NeedsDraw); + Assert.True (view.Padding!.NeedsDraw); + } + + #endregion + + #region SubViewNeedsDraw Tests + + [Fact] + public void SubViewNeedsDraw_InitiallyFalse () + { + IDriver driver = CreateFakeDriver (80, 25); + driver.Clip = new Region (driver.Screen); + + var view = new View { Width = 10, Height = 10, Driver = driver }; + view.BeginInit (); + view.EndInit (); + view.Draw (); // Draw once to clear initial NeedsDraw + + Assert.False (view.SubViewNeedsDraw); + } + + [Fact] + public void SetSubViewNeedsDraw_PropagatesUp () + { + var grandparent = new View { X = 0, Y = 0, Width = 100, Height = 100 }; + var parent = new View { X = 10, Y = 10, Width = 50, Height = 50 }; + var child = new View { X = 5, Y = 5, Width = 20, Height = 20 }; + + grandparent.Add (parent); + parent.Add (child); + grandparent.BeginInit (); + grandparent.EndInit (); + grandparent.LayoutSubViews (); + + child.SetSubViewNeedsDraw (); + + Assert.True (child.SubViewNeedsDraw); + Assert.True (parent.SubViewNeedsDraw); + Assert.True (grandparent.SubViewNeedsDraw); + } + + [Fact] + public void SubViewNeedsDraw_ClearedAfterDraw () + { + IDriver driver = CreateFakeDriver (80, 25); + driver.Clip = new Region (driver.Screen); + + var parent = new View + { + X = 0, + Y = 0, + Width = 50, + Height = 50, + Driver = driver + }; + var child = new View { X = 10, Y = 10, Width = 20, Height = 20 }; + parent.Add (child); + parent.BeginInit (); + parent.EndInit (); + parent.LayoutSubViews (); + + child.SetNeedsDraw (); + Assert.True (parent.SubViewNeedsDraw); + + parent.Draw (); + + Assert.False (parent.SubViewNeedsDraw); + Assert.False (child.SubViewNeedsDraw); + } + + #endregion + + #region Draw Visibility Tests + + [Fact] + public void Draw_NotVisible_DoesNotDraw () + { + IDriver driver = CreateFakeDriver (80, 25); + + var view = new View + { + X = 0, + Y = 0, + Width = 10, + Height = 10, + Visible = false, + Driver = driver + }; + view.BeginInit (); + view.EndInit (); + + view.SetNeedsDraw (); + view.Draw (); + + // NeedsDraw should still be false (view wasn't drawn) + Assert.False (view.NeedsDraw); + } + + [Fact] + public void Draw_SuperViewNotVisible_DoesNotDraw () + { + IDriver driver = CreateFakeDriver (80, 25); + + var parent = new View + { + X = 0, + Y = 0, + Width = 50, + Height = 50, + Visible = false, + Driver = driver + }; + var child = new View { X = 10, Y = 10, Width = 20, Height = 20 }; + parent.Add (child); + parent.BeginInit (); + parent.EndInit (); + + child.SetNeedsDraw (); + child.Draw (); + + // Child should not have been drawn + Assert.True (child.NeedsDraw); // Still needs draw + } + + [Fact] + public void Draw_Enabled_False_UsesDisabledAttribute () + { + IDriver driver = CreateFakeDriver (80, 25); + driver.Clip = new Region (driver.Screen); + + bool drawingTextCalled = false; + Attribute? usedAttribute = null; + + var view = new TestView + { + X = 0, + Y = 0, + Width = 10, + Height = 10, + Enabled = false, + Driver = driver + }; + view.BeginInit (); + view.EndInit (); + view.LayoutSubViews (); + + view.DrawingText += (s, e) => + { + drawingTextCalled = true; + usedAttribute = driver.CurrentAttribute; + }; + + view.Draw (); + + Assert.True (drawingTextCalled); + Assert.NotNull (usedAttribute); + // The disabled attribute should have been used + Assert.Equal (view.GetAttributeForRole (VisualRole.Disabled), usedAttribute); + } + + #endregion + + #region Draw Order Tests + + [Fact] + public void Draw_CallsMethodsInCorrectOrder () + { + IDriver driver = CreateFakeDriver (80, 25); + driver.Clip = new Region (driver.Screen); + + var callOrder = new List (); + + var view = new TestView + { + X = 0, + Y = 0, + Width = 20, + Height = 20, + Driver = driver + }; + view.BeginInit (); + view.EndInit (); + view.LayoutSubViews (); + + view.DrawingAdornmentsCallback = () => callOrder.Add ("DrawingAdornments"); + view.ClearingViewportCallback = () => callOrder.Add ("ClearingViewport"); + view.DrawingSubViewsCallback = () => callOrder.Add ("DrawingSubViews"); + view.DrawingTextCallback = () => callOrder.Add ("DrawingText"); + view.DrawingContentCallback = () => callOrder.Add ("DrawingContent"); + view.RenderingLineCanvasCallback = () => callOrder.Add ("RenderingLineCanvas"); + view.DrawCompleteCallback = () => callOrder.Add ("DrawComplete"); + + view.Draw (); + + Assert.Equal ( + new [] { "DrawingAdornments", "ClearingViewport", "DrawingSubViews", "DrawingText", "DrawingContent", "RenderingLineCanvas", "DrawComplete" }, + callOrder + ); + } + + [Fact] + public void Draw_WithSubViews_DrawsInReverseOrder () + { + IDriver driver = CreateFakeDriver (80, 25); + driver.Clip = new Region (driver.Screen); + + var drawOrder = new List (); + + var parent = new View + { + X = 0, + Y = 0, + Width = 50, + Height = 50, + Driver = driver + }; + + var child1 = new TestView { X = 0, Y = 0, Width = 10, Height = 10, Id = "Child1" }; + var child2 = new TestView { X = 0, Y = 10, Width = 10, Height = 10, Id = "Child2" }; + var child3 = new TestView { X = 0, Y = 20, Width = 10, Height = 10, Id = "Child3" }; + + parent.Add (child1); + parent.Add (child2); + parent.Add (child3); + + parent.BeginInit (); + parent.EndInit (); + parent.LayoutSubViews (); + + child1.DrawingContentCallback = () => drawOrder.Add ("Child1"); + child2.DrawingContentCallback = () => drawOrder.Add ("Child2"); + child3.DrawingContentCallback = () => drawOrder.Add ("Child3"); + + parent.Draw (); + + // SubViews are drawn in reverse order for clipping optimization + Assert.Equal (new [] { "Child3", "Child2", "Child1" }, drawOrder); + } + + #endregion + + #region DrawContext Tests + + [Fact] + public void Draw_WithContext_PassesContext () + { + IDriver driver = CreateFakeDriver (80, 25); + driver.Clip = new Region (driver.Screen); + + DrawContext? receivedContext = null; + + var view = new TestView + { + X = 0, + Y = 0, + Width = 20, + Height = 20, + Driver = driver + }; + view.BeginInit (); + view.EndInit (); + view.LayoutSubViews (); + + view.DrawingContentCallback = () => { }; + view.DrawingContent += (s, e) => + { + receivedContext = e.DrawContext; + }; + + var context = new DrawContext (); + view.Draw (context); + + Assert.NotNull (receivedContext); + Assert.Equal (context, receivedContext); + } + + [Fact] + public void Draw_WithoutContext_CreatesContext () + { + IDriver driver = CreateFakeDriver (80, 25); + driver.Clip = new Region (driver.Screen); + + DrawContext? receivedContext = null; + + var view = new TestView + { + X = 0, + Y = 0, + Width = 20, + Height = 20, + Driver = driver + }; + view.BeginInit (); + view.EndInit (); + view.LayoutSubViews (); + + view.DrawingContentCallback = () => { }; + view.DrawingContent += (s, e) => + { + receivedContext = e.DrawContext; + }; + + view.Draw (); + + Assert.NotNull (receivedContext); + } + + #endregion + + #region Event Tests + + [Fact] + public void ClearingViewport_CanCancel () + { + IDriver driver = CreateFakeDriver (80, 25); + driver.Clip = new Region (driver.Screen); + + var view = new View + { + X = 0, + Y = 0, + Width = 20, + Height = 20, + Driver = driver + }; + view.BeginInit (); + view.EndInit (); + view.LayoutSubViews (); + + bool clearedCalled = false; + + view.ClearingViewport += (s, e) => e.Cancel = true; + view.ClearedViewport += (s, e) => clearedCalled = true; + + view.Draw (); + + Assert.False (clearedCalled); + } + + [Fact] + public void DrawingText_CanCancel () + { + IDriver driver = CreateFakeDriver (80, 25); + driver.Clip = new Region (driver.Screen); + + var view = new View + { + X = 0, + Y = 0, + Width = 20, + Height = 20, + Driver = driver, + Text = "Test" + }; + view.BeginInit (); + view.EndInit (); + view.LayoutSubViews (); + + bool drewTextCalled = false; + + view.DrawingText += (s, e) => e.Cancel = true; + view.DrewText += (s, e) => drewTextCalled = true; + + view.Draw (); + + Assert.False (drewTextCalled); + } + + [Fact] + public void DrawingSubViews_CanCancel () + { + IDriver driver = CreateFakeDriver (80, 25); + driver.Clip = new Region (driver.Screen); + + var parent = new TestView + { + X = 0, + Y = 0, + Width = 50, + Height = 50, + Driver = driver + }; + var child = new TestView { X = 10, Y = 10, Width = 20, Height = 20 }; + parent.Add (child); + parent.BeginInit (); + parent.EndInit (); + parent.LayoutSubViews (); + + bool childDrawn = false; + child.DrawingContentCallback = () => childDrawn = true; + + parent.DrawingSubViews += (s, e) => e.Cancel = true; + + parent.Draw (); + + Assert.False (childDrawn); + } + + [Fact] + public void DrawComplete_AlwaysCalled () + { + IDriver driver = CreateFakeDriver (80, 25); + driver.Clip = new Region (driver.Screen); + + bool drawCompleteCalled = false; + + var view = new View + { + X = 0, + Y = 0, + Width = 20, + Height = 20, + Driver = driver + }; + view.BeginInit (); + view.EndInit (); + view.LayoutSubViews (); + + view.DrawComplete += (s, e) => drawCompleteCalled = true; + + view.Draw (); + + Assert.True (drawCompleteCalled); + } + + #endregion + + #region Transparent View Tests + + [Fact] + public void Draw_TransparentView_DoesNotClearViewport () + { + IDriver driver = CreateFakeDriver (80, 25); + driver.Clip = new Region (driver.Screen); + + bool clearedViewport = false; + + var view = new View + { + X = 0, + Y = 0, + Width = 20, + Height = 20, + Driver = driver, + ViewportSettings = ViewportSettingsFlags.Transparent + }; + view.BeginInit (); + view.EndInit (); + view.LayoutSubViews (); + + view.ClearedViewport += (s, e) => clearedViewport = true; + + view.Draw (); + + Assert.False (clearedViewport); + } + + [Fact] + public void Draw_TransparentView_ExcludesDrawnRegionFromClip () + { + IDriver driver = CreateFakeDriver (80, 25); + var initialClip = new Region (driver.Screen); + driver.Clip = initialClip; + Application.Driver = driver; + + var view = new View + { + X = 10, + Y = 10, + Width = 20, + Height = 20, + Driver = driver, + ViewportSettings = ViewportSettingsFlags.Transparent + }; + view.BeginInit (); + view.EndInit (); + view.LayoutSubViews (); + + view.Draw (); + + // The drawn area should be excluded from the clip + Rectangle viewportScreen = view.ViewportToScreen (view.Viewport); + + // Points inside the view should be excluded + // Note: This test depends on the DrawContext tracking, which may not exclude if nothing was actually drawn + // We're verifying the mechanism exists, not that it necessarily excludes in this specific case + + Application.ResetState (true); + } + + #endregion + + #region Helper Test View + + private class TestView : View + { + public Action? DrawingAdornmentsCallback { get; set; } + public Action? ClearingViewportCallback { get; set; } + public Action? DrawingSubViewsCallback { get; set; } + public Action? DrawingTextCallback { get; set; } + public Action? DrawingContentCallback { get; set; } + public Action? RenderingLineCanvasCallback { get; set; } + public Action? DrawCompleteCallback { get; set; } + + protected override bool OnDrawingAdornments () + { + DrawingAdornmentsCallback?.Invoke (); + return base.OnDrawingAdornments (); + } + + protected override bool OnClearingViewport () + { + ClearingViewportCallback?.Invoke (); + return base.OnClearingViewport (); + } + + protected override bool OnDrawingSubViews (DrawContext? context) + { + DrawingSubViewsCallback?.Invoke (); + return base.OnDrawingSubViews (context); + } + + protected override bool OnDrawingText (DrawContext? context) + { + DrawingTextCallback?.Invoke (); + return base.OnDrawingText (context); + } + + protected override bool OnDrawingContent (DrawContext? context) + { + DrawingContentCallback?.Invoke (); + return base.OnDrawingContent (context); + } + + protected override bool OnRenderingLineCanvas () + { + RenderingLineCanvasCallback?.Invoke (); + return base.OnRenderingLineCanvas (); + } + + protected override void OnDrawComplete (DrawContext? context) + { + DrawCompleteCallback?.Invoke (); + base.OnDrawComplete (context); + } + } + + #endregion +} diff --git a/Tests/UnitTestsParallelizable/View/Layout/GetViewsUnderLocationTests.cs b/Tests/UnitTestsParallelizable/View/Layout/GetViewsUnderLocationTests.cs index 4bb0506c1..dd5cede33 100644 --- a/Tests/UnitTestsParallelizable/View/Layout/GetViewsUnderLocationTests.cs +++ b/Tests/UnitTestsParallelizable/View/Layout/GetViewsUnderLocationTests.cs @@ -20,7 +20,7 @@ public class GetViewsUnderLocationTests var location = new Point (testX, testY); // Act - List viewsUnderMouse = View.GetViewsUnderLocation (location, ViewportSettingsFlags.None); + List viewsUnderMouse = view.GetViewsUnderLocation (location, ViewportSettingsFlags.None); // Assert Assert.Empty (viewsUnderMouse); @@ -42,7 +42,7 @@ public class GetViewsUnderLocationTests var location = new Point (testX, testY); // Act - List viewsUnderMouse = View.GetViewsUnderLocation (location, ViewportSettingsFlags.None); + List viewsUnderMouse = view.GetViewsUnderLocation (location, ViewportSettingsFlags.None); // Assert Assert.Empty (viewsUnderMouse); diff --git a/Tests/UnitTestsParallelizable/Views/AllViewsDrawTests.cs b/Tests/UnitTestsParallelizable/Views/AllViewsDrawTests.cs index 35ebdf600..1926538c8 100644 --- a/Tests/UnitTestsParallelizable/Views/AllViewsDrawTests.cs +++ b/Tests/UnitTestsParallelizable/Views/AllViewsDrawTests.cs @@ -21,6 +21,7 @@ public class AllViewsDrawTests (ITestOutputHelper output) : TestsAllViews return; } + view.Driver = driver; output.WriteLine ($"Testing {viewType}"); if (view is IDesignable designable) diff --git a/Tests/UnitTestsParallelizable/Views/LineTests.cs b/Tests/UnitTestsParallelizable/Views/LineTests.cs index 8fa44f4a1..a0aa17ca9 100644 --- a/Tests/UnitTestsParallelizable/Views/LineTests.cs +++ b/Tests/UnitTestsParallelizable/Views/LineTests.cs @@ -1,6 +1,8 @@ +using UnitTests; + namespace UnitTests_Parallelizable.ViewsTests; -public class LineTests +public class LineTests : FakeDriverBase { [Fact] public void Line_DefaultConstructor_Horizontal () @@ -87,7 +89,7 @@ public class LineTests [Fact] public void Line_DrawsCalled_Successfully () { - var app = new Window (); + var app = new Window () { Driver = CreateFakeDriver () }; var line = new Line { Y = 1, Width = 10 }; app.Add (line); @@ -103,7 +105,7 @@ public class LineTests [Fact] public void Line_WithBorder_DrawsSuccessfully () { - var app = new Window { Width = 20, Height = 10, BorderStyle = LineStyle.Single }; + var app = new Window { Driver = CreateFakeDriver (), Width = 20, Height = 10, BorderStyle = LineStyle.Single }; // Add a line that intersects with the window border var line = new Line { X = 5, Y = 0, Height = Dim.Fill (), Orientation = Orientation.Vertical }; @@ -121,7 +123,7 @@ public class LineTests [Fact] public void Line_MultipleIntersecting_DrawsSuccessfully () { - var app = new Window { Width = 30, Height = 15 }; + var app = new Window { Driver = CreateFakeDriver (), Width = 30, Height = 15 }; // Create intersecting lines var hLine = new Line { X = 5, Y = 5, Width = 15, Style = LineStyle.Single }; @@ -258,7 +260,7 @@ public class LineTests // Test: new Line { Height = 9, Orientation = Orientation.Vertical } // Expected: Width=1, Height=9 - line = new() { Height = 9, Orientation = Orientation.Vertical }; + line = new () { Height = 9, Orientation = Orientation.Vertical }; Assert.Equal (1, line.Width.GetAnchor (0)); Assert.Equal (9, line.Height.GetAnchor (0)); diff --git a/Tests/UnitTestsParallelizable/Views/ScrollSliderTests.cs b/Tests/UnitTestsParallelizable/Views/ScrollSliderTests.cs index 89d9649c1..2f510d9a4 100644 --- a/Tests/UnitTestsParallelizable/Views/ScrollSliderTests.cs +++ b/Tests/UnitTestsParallelizable/Views/ScrollSliderTests.cs @@ -1,8 +1,9 @@ -using Xunit.Abstractions; +using UnitTests; +using Xunit.Abstractions; namespace UnitTests_Parallelizable.ViewsTests; -public class ScrollSliderTests +public class ScrollSliderTests (ITestOutputHelper output) : FakeDriverBase { [Fact] public void Constructor_Initializes_Correctly () @@ -675,4 +676,340 @@ public class ScrollSliderTests Assert.True (scrollSlider.Position <= 5); } + + + [Theory] + [SetupFakeApplication] + [InlineData ( + 3, + 10, + 1, + 0, + Orientation.Vertical, + @" +┌───┐ +│███│ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +└───┘")] + [InlineData ( + 10, + 1, + 3, + 0, + Orientation.Horizontal, + @" +┌──────────┐ +│███ │ +└──────────┘")] + [InlineData ( + 3, + 10, + 3, + 0, + Orientation.Vertical, + @" +┌───┐ +│███│ +│███│ +│███│ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +└───┘")] + + + + [InlineData ( + 3, + 10, + 5, + 0, + Orientation.Vertical, + @" +┌───┐ +│███│ +│███│ +│███│ +│███│ +│███│ +│ │ +│ │ +│ │ +│ │ +│ │ +└───┘")] + + [InlineData ( + 3, + 10, + 5, + 1, + Orientation.Vertical, + @" +┌───┐ +│ │ +│███│ +│███│ +│███│ +│███│ +│███│ +│ │ +│ │ +│ │ +│ │ +└───┘")] + [InlineData ( + 3, + 10, + 5, + 4, + Orientation.Vertical, + @" +┌───┐ +│ │ +│ │ +│ │ +│ │ +│███│ +│███│ +│███│ +│███│ +│███│ +│ │ +└───┘")] + [InlineData ( + 3, + 10, + 5, + 5, + Orientation.Vertical, + @" +┌───┐ +│ │ +│ │ +│ │ +│ │ +│ │ +│███│ +│███│ +│███│ +│███│ +│███│ +└───┘")] + [InlineData ( + 3, + 10, + 5, + 6, + Orientation.Vertical, + @" +┌───┐ +│ │ +│ │ +│ │ +│ │ +│ │ +│███│ +│███│ +│███│ +│███│ +│███│ +└───┘")] + + [InlineData ( + 3, + 10, + 10, + 0, + Orientation.Vertical, + @" +┌───┐ +│███│ +│███│ +│███│ +│███│ +│███│ +│███│ +│███│ +│███│ +│███│ +│███│ +└───┘")] + + [InlineData ( + 3, + 10, + 10, + 5, + Orientation.Vertical, + @" +┌───┐ +│███│ +│███│ +│███│ +│███│ +│███│ +│███│ +│███│ +│███│ +│███│ +│███│ +└───┘")] + [InlineData ( + 3, + 10, + 11, + 0, + Orientation.Vertical, + @" +┌───┐ +│███│ +│███│ +│███│ +│███│ +│███│ +│███│ +│███│ +│███│ +│███│ +│███│ +└───┘")] + + [InlineData ( + 10, + 3, + 5, + 0, + Orientation.Horizontal, + @" +┌──────────┐ +│█████ │ +│█████ │ +│█████ │ +└──────────┘")] + + [InlineData ( + 10, + 3, + 5, + 1, + Orientation.Horizontal, + @" +┌──────────┐ +│ █████ │ +│ █████ │ +│ █████ │ +└──────────┘")] + [InlineData ( + 10, + 3, + 5, + 4, + Orientation.Horizontal, + @" +┌──────────┐ +│ █████ │ +│ █████ │ +│ █████ │ +└──────────┘")] + [InlineData ( + 10, + 3, + 5, + 5, + Orientation.Horizontal, + @" +┌──────────┐ +│ █████│ +│ █████│ +│ █████│ +└──────────┘")] + [InlineData ( + 10, + 3, + 5, + 6, + Orientation.Horizontal, + @" +┌──────────┐ +│ █████│ +│ █████│ +│ █████│ +└──────────┘")] + + [InlineData ( + 10, + 3, + 10, + 0, + Orientation.Horizontal, + @" +┌──────────┐ +│██████████│ +│██████████│ +│██████████│ +└──────────┘")] + + [InlineData ( + 10, + 3, + 10, + 5, + Orientation.Horizontal, + @" +┌──────────┐ +│██████████│ +│██████████│ +│██████████│ +└──────────┘")] + [InlineData ( + 10, + 3, + 11, + 0, + Orientation.Horizontal, + @" +┌──────────┐ +│██████████│ +│██████████│ +│██████████│ +└──────────┘")] + public void Draws_Correctly (int superViewportWidth, int superViewportHeight, int sliderSize, int position, Orientation orientation, string expected) + { + IDriver driver = CreateFakeDriver (); + var super = new Window + { + Driver = driver, + Id = "super", + Width = superViewportWidth + 2, + Height = superViewportHeight + 2 + }; + + var scrollSlider = new ScrollSlider + { + Orientation = orientation, + Size = sliderSize, + //Position = position, + }; + Assert.Equal (sliderSize, scrollSlider.Size); + super.Add (scrollSlider); + scrollSlider.Position = position; + + super.Layout (); + super.Draw (); + + _ = DriverAssert.AssertDriverContentsWithFrameAre (expected, output, driver); + } } diff --git a/Tests/UnitTestsParallelizable/Views/ShortcutTests.cs b/Tests/UnitTestsParallelizable/Views/ShortcutTests.cs index 7866055ca..fc740c63a 100644 --- a/Tests/UnitTestsParallelizable/Views/ShortcutTests.cs +++ b/Tests/UnitTestsParallelizable/Views/ShortcutTests.cs @@ -108,7 +108,7 @@ public class ShortcutTests // | C H K | Assert.Equal (expectedWidth, shortcut.Frame.Width); - shortcut = new() + shortcut = new () { HelpText = help, Title = command, @@ -118,7 +118,7 @@ public class ShortcutTests shortcut.Layout (); Assert.Equal (expectedWidth, shortcut.Frame.Width); - shortcut = new() + shortcut = new () { HelpText = help, Key = key, @@ -128,7 +128,7 @@ public class ShortcutTests shortcut.Layout (); Assert.Equal (expectedWidth, shortcut.Frame.Width); - shortcut = new() + shortcut = new () { Key = key, HelpText = help, @@ -314,13 +314,16 @@ public class ShortcutTests shortcut.Key = Key.A; Assert.True (shortcut.HotKeyBindings.TryGet (Key.A, out _)); + shortcut.App = Application.Create (); shortcut.BindKeyToApplication = true; + shortcut.BeginInit (); + shortcut.EndInit (); Assert.False (shortcut.HotKeyBindings.TryGet (Key.A, out _)); - Assert.True (Application.KeyBindings.TryGet (Key.A, out _)); + Assert.True (shortcut.App?.Keyboard.KeyBindings.TryGet (Key.A, out _)); shortcut.BindKeyToApplication = false; Assert.True (shortcut.HotKeyBindings.TryGet (Key.A, out _)); - Assert.False (Application.KeyBindings.TryGet (Key.A, out _)); + Assert.False (shortcut.App?.Keyboard.KeyBindings.TryGet (Key.A, out _)); } [Theory] diff --git a/codecov.yml b/codecov.yml index ea178f7c1..b71daa005 100644 --- a/codecov.yml +++ b/codecov.yml @@ -11,7 +11,7 @@ coverage: # Overall project coverage project: default: - target: 70% # Minimum target coverage + target: 75% # Minimum target coverage threshold: 1% # Allow 1% decrease without failing base: auto # Compare against base branch (v2_develop) if_ci_failed: error # Fail if CI fails diff --git a/docfx/docs/View.md b/docfx/docs/View.md index f65ab2de5..33ac685fd 100644 --- a/docfx/docs/View.md +++ b/docfx/docs/View.md @@ -32,6 +32,8 @@ See the [Views Overview](views.md) for a catalog of all built-in View subclasses - [View.SuperView](~/api/Terminal.Gui.ViewBase.View.yml#Terminal_Gui_ViewBase_View_SuperView) - The View's container (null if the View has no container) - [View.Id](~/api/Terminal.Gui.ViewBase.View.yml#Terminal_Gui_ViewBase_View_Id) - Unique identifier for the View (should be unique among siblings) - [View.Data](~/api/Terminal.Gui.ViewBase.View.yml#Terminal_Gui_ViewBase_View_Data) - Arbitrary data attached to the View +- [View.App](~/api/Terminal.Gui.ViewBase.View.yml#Terminal_Gui_ViewBase_View_App) - The application context this View belongs to +- [View.Driver](~/api/Terminal.Gui.ViewBase.View.yml#Terminal_Gui_ViewBase_View_Driver) - The driver used for rendering (derived from App). This is a shortcut to `App.Driver` for convenience. --- @@ -103,6 +105,8 @@ Views implement [ISupportInitializeNotification](https://docs.microsoft.com/en-u 3. **[EndInit](~/api/Terminal.Gui.ViewBase.View.yml#Terminal_Gui_ViewBase_View_EndInit)** - Signals initialization is complete; raises [View.Initialized](~/api/Terminal.Gui.ViewBase.View.yml) event 4. **[IsInitialized](~/api/Terminal.Gui.ViewBase.View.yml#Terminal_Gui_ViewBase_View_IsInitialized)** - Property indicating if initialization is complete +During initialization, [View.App](~/api/Terminal.Gui.ViewBase.View.yml#Terminal_Gui_ViewBase_View_App) is set to reference the application context, enabling views to access application services like the driver and current session. + ### Disposal Views are [IDisposable](https://docs.microsoft.com/en-us/dotnet/api/system.idisposable): @@ -678,6 +682,7 @@ view.ShadowStyle = ShadowStyle.Transparent; ## See Also +- **[Application Deep Dive](application.md)** - Instance-based application architecture - **[Views Overview](views.md)** - Complete list of all built-in Views - **[Layout Deep Dive](layout.md)** - Detailed layout system documentation - **[Drawing Deep Dive](drawing.md)** - Drawing system and color management diff --git a/docfx/docs/application.md b/docfx/docs/application.md new file mode 100644 index 000000000..b999b1fc0 --- /dev/null +++ b/docfx/docs/application.md @@ -0,0 +1,552 @@ +# Application Architecture + +Terminal.Gui v2 uses an instance-based application architecture that decouples views from the global application state, improving testability and enabling multiple application contexts. + +## View Hierarchy and Run Stack + +```mermaid +graph TB + subgraph ViewTree["View Hierarchy (SuperView/SubView)"] + direction TB + Top[Application.Current
Window] + Menu[MenuBar] + Status[StatusBar] + Content[Content View] + Button1[Button] + Button2[Button] + + Top --> Menu + Top --> Status + Top --> Content + Content --> Button1 + Content --> Button2 + end + + subgraph Stack["Application.SessionStack"] + direction TB + S1[Window
Currently Active] + S2[Previous Toplevel
Waiting] + S3[Base Toplevel
Waiting] + + S1 -.-> S2 -.-> S3 + end + + Top -.->|"same instance"| S1 + + style Top fill:#ccffcc,stroke:#339933,stroke-width:3px + style S1 fill:#ccffcc,stroke:#339933,stroke-width:3px +``` + +## Usage Example Flow + +```mermaid +sequenceDiagram + participant App as Application + participant Main as Main Window + participant Dialog as Dialog + + Note over App: Initially empty SessionStack + + App->>Main: Run(mainWindow) + activate Main + Note over App: SessionStack: [Main]
Current: Main + + Main->>Dialog: Run(dialog) + activate Dialog + Note over App: SessionStack: [Dialog, Main]
Current: Dialog + + Dialog->>App: RequestStop() + deactivate Dialog + Note over App: SessionStack: [Main]
Current: Main + + Main->>App: RequestStop() + deactivate Main + Note over App: SessionStack: []
Current: null +``` + +## Key Concepts + +### Instance-Based vs Static + +**Terminal.Gui v2** has transitioned from a static singleton pattern to an instance-based architecture: + +```csharp +// OLD (v1 / early v2 - now obsolete): +Application.Init(); +Application.Top.Add(myView); +Application.Run(); +Application.Shutdown(); + +// NEW (v2 instance-based): +var app = Application.Create (); +app.Init(); +var top = new Toplevel(); +top.Add(myView); +app.Run(top); +app.Shutdown(); +``` + +### View.App Property + +Every view now has an `App` property that references its application context: + +```csharp +public class View +{ + /// + /// Gets the application context for this view. + /// + public IApplication? App { get; internal set; } + + /// + /// Gets the application context, checking parent hierarchy if needed. + /// Override to customize application resolution. + /// + public virtual IApplication? GetApp() => App ?? SuperView?.GetApp(); +} +``` + +**Benefits:** +- Views can be tested without `Application.Init()` +- Multiple applications can coexist +- Clear ownership: views know their context +- Reduced global state dependencies + +### Accessing Application from Views + +**Recommended pattern:** + +```csharp +public class MyView : View +{ + public override void OnEnter(View view) + { + // Use View.App instead of static Application + App?.Current?.SetNeedsDraw(); + + // Access SessionStack + if (App?.SessionStack.Count > 0) + { + // Work with sessions + } + } +} +``` + +**Alternative - dependency injection:** + +```csharp +public class MyView : View +{ + private readonly IApplication _app; + + public MyView(IApplication app) + { + _app = app; + // Now completely decoupled from static Application + } + + public void DoWork() + { + _app.Current?.SetNeedsDraw(); + } +} +``` + +## IApplication Interface + +The `IApplication` interface defines the application contract: + +```csharp +public interface IApplication +{ + /// + /// Gets the currently running Toplevel (the "current session"). + /// Renamed from "Top" for clarity. + /// + Toplevel? Current { get; } + + /// + /// Gets the stack of running sessions. + /// Renamed from "TopLevels" to align with SessionToken terminology. + /// + ConcurrentStack SessionStack { get; } + + IDriver? Driver { get; } + IMainLoopCoordinator? MainLoop { get; } + + void Init(string? driverName = null); + void Shutdown(); + SessionToken? Begin(Toplevel toplevel); + void End(SessionToken sessionToken); + // ... other members +} +``` + +## Terminology Changes + +Terminal.Gui v2 modernized its terminology for clarity: + +### Application.Current (formerly "Top") + +The `Current` property represents the currently running Toplevel (the active session): + +```csharp +// Access the current session +Toplevel? current = app.Current; + +// From within a view +Toplevel? current = App?.Current; +``` + +**Why "Current" instead of "Top"?** +- Follows .NET patterns (`Thread.CurrentThread`, `HttpContext.Current`) +- Self-documenting: immediately clear it's the "current" active view +- Less confusing than "Top" which could mean "topmost in Z-order" + +### Application.SessionStack (formerly "TopLevels") + +The `SessionStack` property is the stack of running sessions: + +```csharp +// Access all running sessions +foreach (var toplevel in app.SessionStack) +{ + // Process each session +} + +// From within a view +int sessionCount = App?.SessionStack.Count ?? 0; +``` + +**Why "SessionStack" instead of "TopLevels"?** +- Describes both content (sessions) and structure (stack) +- Aligns with `SessionToken` terminology +- Follows .NET naming patterns (descriptive + collection type) + +## Migration from Static Application + +The static `Application` class now delegates to `ApplicationImpl.Instance` and is marked obsolete: + +```csharp +public static class Application +{ + [Obsolete("Use ApplicationImpl.Instance.Current or view.App?.Current")] + public static Toplevel? Current => Instance?.Current; + + [Obsolete("Use ApplicationImpl.Instance.SessionStack or view.App?.SessionStack")] + public static ConcurrentStack SessionStack => Instance?.SessionStack ?? new(); +} +``` + +### Migration Strategies + +**Strategy 1: Use View.App** + +```csharp +// OLD: +void MyMethod() +{ + Application.Current?.SetNeedsDraw(); +} + +// NEW: +void MyMethod(View view) +{ + view.App?.Current?.SetNeedsDraw(); +} +``` + +**Strategy 2: Pass IApplication** + +```csharp +// OLD: +void ProcessSessions() +{ + foreach (var toplevel in Application.SessionStack) + { + // Process + } +} + +// NEW: +void ProcessSessions(IApplication app) +{ + foreach (var toplevel in app.SessionStack) + { + // Process + } +} +``` + +**Strategy 3: Store IApplication Reference** + +```csharp +public class MyService +{ + private readonly IApplication _app; + + public MyService(IApplication app) + { + _app = app; + } + + public void DoWork() + { + _app.Current?.Title = "Processing..."; + } +} +``` + +## Session Management + +### Begin and End + +Applications manage sessions through `Begin()` and `End()`: + +```csharp +var app = Application.Create (); +app.Init(); + +var toplevel = new Toplevel(); + +// Begin a new session - pushes to SessionStack +SessionToken? token = app.Begin(toplevel); + +// Current now points to this toplevel +Debug.Assert(app.Current == toplevel); + +// End the session - pops from SessionStack +if (token != null) +{ + app.End(token); +} + +// Current restored to previous toplevel (if any) +``` + +### Nested Sessions + +Multiple sessions can run nested: + +```csharp +var app = Application.Create (); +app.Init(); + +// Session 1 +var main = new Toplevel { Title = "Main" }; +var token1 = app.Begin(main); +// app.Current == main, SessionStack.Count == 1 + +// Session 2 (nested) +var dialog = new Dialog { Title = "Dialog" }; +var token2 = app.Begin(dialog); +// app.Current == dialog, SessionStack.Count == 2 + +// End dialog +app.End(token2); +// app.Current == main, SessionStack.Count == 1 + +// End main +app.End(token1); +// app.Current == null, SessionStack.Count == 0 +``` + +## View.Driver Property + +Similar to `View.App`, views now have a `Driver` property: + +```csharp +public class View +{ + /// + /// Gets the driver for this view. + /// + public IDriver? Driver => GetDriver(); + + /// + /// Gets the driver, checking application context if needed. + /// Override to customize driver resolution. + /// + public virtual IDriver? GetDriver() => App?.Driver; +} +``` + +**Usage:** + +```csharp +public override void OnDrawContent(Rectangle viewport) +{ + // Use view's driver instead of Application.Driver + Driver?.Move(0, 0); + Driver?.AddStr("Hello"); +} +``` + +## Testing with the New Architecture + +The instance-based architecture dramatically improves testability: + +### Testing Views in Isolation + +```csharp +[Fact] +public void MyView_DisplaysCorrectly() +{ + // Create mock application + var mockApp = new Mock(); + mockApp.Setup(a => a.Current).Returns(new Toplevel()); + + // Create view with mock app + var view = new MyView { App = mockApp.Object }; + + // Test without Application.Init()! + view.SetNeedsDraw(); + Assert.True(view.NeedsDraw); + + // No Application.Shutdown() needed! +} +``` + +### Testing with Real ApplicationImpl + +```csharp +[Fact] +public void MyView_WorksWithRealApplication() +{ + var app = Application.Create (); + try + { + app.Init(new FakeDriver()); + + var view = new MyView(); + var top = new Toplevel(); + top.Add(view); + + app.Begin(top); + + // View.App automatically set + Assert.NotNull(view.App); + Assert.Same(app, view.App); + + // Test view behavior + view.DoSomething(); + + } + finally + { + app.Shutdown(); + } +} +``` + +## Best Practices + +### DO: Use View.App + +```csharp +✅ GOOD: +public void Refresh() +{ + App?.Current?.SetNeedsDraw(); +} +``` + +### DON'T: Use Static Application + +```csharp +❌ AVOID: +public void Refresh() +{ + Application.Current?.SetNeedsDraw(); // Obsolete! +} +``` + +### DO: Pass IApplication as Dependency + +```csharp +✅ GOOD: +public class Service +{ + public Service(IApplication app) { } +} +``` + +### DON'T: Assume Application.Instance Exists + +```csharp +❌ AVOID: +public class Service +{ + public void DoWork() + { + var app = Application.Instance; // Might be null! + } +} +``` + +### DO: Override GetApp() for Custom Resolution + +```csharp +✅ GOOD: +public class SpecialView : View +{ + private IApplication? _customApp; + + public override IApplication? GetApp() + { + return _customApp ?? base.GetApp(); + } +} +``` + +## Advanced Scenarios + +### Multiple Applications + +The instance-based architecture enables multiple applications: + +```csharp +// Application 1 +var app1 = Application.Create (); +app1.Init(new WindowsDriver()); +var top1 = new Toplevel { Title = "App 1" }; +// ... configure top1 + +// Application 2 (different driver!) +var app2 = Application.Create (); +app2.Init(new CursesDriver()); +var top2 = new Toplevel { Title = "App 2" }; +// ... configure top2 + +// Views in top1 use app1 +// Views in top2 use app2 +``` + +### Application-Agnostic Views + +Create views that work with any application: + +```csharp +public class UniversalView : View +{ + public void ShowMessage(string message) + { + // Works regardless of which application context + var app = GetApp(); + if (app != null) + { + var msg = new MessageBox(message); + app.Begin(msg); + } + } +} +``` + +## See Also + +- [Navigation](navigation.md) - Navigation with the instance-based architecture +- [Keyboard](keyboard.md) - Keyboard handling through View.App +- [Mouse](mouse.md) - Mouse handling through View.App +- [Drivers](drivers.md) - Driver access through View.Driver +- [Multitasking](multitasking.md) - Session management with SessionStack diff --git a/docfx/docs/config.md b/docfx/docs/config.md index 48a993033..4a549d5ce 100644 --- a/docfx/docs/config.md +++ b/docfx/docs/config.md @@ -459,7 +459,7 @@ ThemeManager.ThemeChanged += (sender, e) => { // Theme has changed // Refresh all views to use new theme - Application.Top?.SetNeedsDraw(); + Application.Current?.SetNeedsDraw(); }; ``` diff --git a/docfx/docs/drawing.md b/docfx/docs/drawing.md index 598ec7a38..a74edf432 100644 --- a/docfx/docs/drawing.md +++ b/docfx/docs/drawing.md @@ -8,7 +8,7 @@ Terminal.Gui provides a set of APIs for formatting text, line drawing, and chara # View Drawing API -Terminal.Gui apps draw using the @Terminal.Gui.ViewBase.View.Move(System.Int32,System.Int32) and @Terminal.Gui.ViewBase.View.AddRune(System.Text.Rune) APIs. Move selects the column and row of the cell and AddRune places the specified glyph in that cell using the @Terminal.Gui.Drawing.Attribute that was most recently set via @Terminal.Gui.ViewBase.View.SetAttribute(Terminal.Gui.Drawing.Attribute). The @Terminal.Gui.Drivers.ConsoleDriver caches all changed Cells and efficiently outputs them to the terminal each iteration of the Application. In other words, Terminal.Gui uses deferred rendering. +Terminal.Gui apps draw using the @Terminal.Gui.ViewBase.View.Move(System.Int32,System.Int32) and @Terminal.Gui.ViewBase.View.AddRune(System.Text.Rune) APIs. Move selects the column and row of the cell and AddRune places the specified glyph in that cell using the @Terminal.Gui.Drawing.Attribute that was most recently set via @Terminal.Gui.ViewBase.View.SetAttribute(Terminal.Gui.Drawing.Attribute). The driver caches all changed Cells and efficiently outputs them to the terminal each iteration of the Application. In other words, Terminal.Gui uses deferred rendering. ## Coordinate System for Drawing @@ -26,7 +26,7 @@ See [Layout](layout.md) for more details of the Terminal.Gui coordinate system. 1) Adding the text to a @Terminal.Gui.Text.TextFormatter object. 2) Setting formatting options, such as @Terminal.Gui.Text.TextFormatter.Alignment. -3) Calling @Terminal.Gui.Text.TextFormatter.Draw(System.Drawing.Rectangle,Terminal.Gui.Drawing.Attribute,Terminal.Gui.Drawing.Attribute,System.Drawing.Rectangle,Terminal.Gui.Drivers.IConsoleDriver). +3) Calling @Terminal.Gui.Text.TextFormatter.Draw(Terminal.Gui.Drivers.IDriver, System.Drawing.Rectangle,Terminal.Gui.Drawing.Attribute,Terminal.Gui.Drawing.Attribute,System.Drawing.Rectangle). ## Line drawing @@ -62,19 +62,17 @@ If a View need to redraw because something changed within it's Content Area it c ## Clipping -Clipping enables better performance and features like transparent margins by ensuring regions of the terminal that need to be drawn actually get drawn by the @Terminal.Gui.Drivers.ConsoleDriver. Terminal.Gui supports non-rectangular clip regions with @Terminal.Gui.Drawing.Region. @Terminal.Gui.Drivers.ConsoleDriver.Clip is the application managed clip region and is managed by @Terminal.Gui.App.Application. Developers cannot change this directly, but can use @Terminal.Gui.ViewBase.View.SetClipToScreen, @Terminal.Gui.ViewBase.View.SetClip(Terminal.Gui.Drawing.Region), @Terminal.Gui.ViewBase.View.SetClipToFrame, etc... +Clipping enables better performance and features like transparent margins by ensuring regions of the terminal that need to be drawn actually get drawn by the driver. Terminal.Gui supports non-rectangular clip regions with @Terminal.Gui.Drawing.Region. The driver.Clip is the application managed clip region and is managed by @Terminal.Gui.App.Application. Developers cannot change this directly, but can use @Terminal.Gui.ViewBase.View.SetClipToScreen, @Terminal.Gui.ViewBase.View.SetClip(Terminal.Gui.Drawing.Region), @Terminal.Gui.ViewBase.View.SetClipToFrame, etc... ## Cell The @Terminal.Gui.Drawing.Cell class represents a single cell on the screen. It contains a character and an attribute. The character is of type `Rune` and the attribute is of type @Terminal.Gui.Drawing.Attribute. -`Cell` is not exposed directly to the developer. Instead, the @Terminal.Gui.Drivers.ConsoleDriver classes manage the `Cell` array that represents the screen. +`Cell` is not exposed directly to the developer. Instead, the driver classes manage the `Cell` array that represents the screen. To draw a `Cell` to the screen, use @Terminal.Gui.ViewBase.View.Move(System.Int32,System.Int32) to specify the row and column coordinates and then use the @Terminal.Gui.ViewBase.View.AddRune(System.Int32,System.Int32,System.Text.Rune) method to draw a single glyph. -// ... existing code ... - ## Attribute The @Terminal.Gui.Drawing.Attribute class represents the formatting attributes of a `Cell`. It exposes properties for the foreground and background colors as well as the text style. The foreground and background colors are of type @Terminal.Gui.Drawing.Color. Bold, underline, and other formatting attributes are supported via the @Terminal.Gui.Drawing.Attribute.Style property. @@ -100,8 +98,6 @@ SetAttributeForRole (VisualRole.Focus); AddStr ("Red on Black Underlined."); ``` -// ... existing code ... - ## VisualRole Represents the semantic visual role of a visual element rendered by a View (e.g., Normal text, Focused item, Active selection). @@ -141,4 +137,30 @@ See [View Deep Dive](View.md) for details. ## Diagnostics -The @Terminal.Gui.ViewBase.ViewDiagnosticFlags.DrawIndicator flag can be set on @Terminal.Gui.ViewBase.View.Diagnostics to cause an animated glyph to appear in the `Border` of each View. The glyph will animate each time that View's `Draw` method is called where either @Terminal.Gui.ViewBase.View.NeedsDraw or @Terminal.Gui.ViewBase.View.SubViewNeedsDraw is set. \ No newline at end of file +The @Terminal.Gui.ViewBase.ViewDiagnosticFlags.DrawIndicator flag can be set on @Terminal.Gui.ViewBase.View.Diagnostics to cause an animated glyph to appear in the `Border` of each View. The glyph will animate each time that View's `Draw` method is called where either @Terminal.Gui.ViewBase.View.NeedsDraw or @Terminal.Gui.ViewBase.View.SubViewNeedsDraw is set. + +## Accessing Application Drawing Context + +Views can access application-level drawing functionality through `View.App`: + +```csharp +public class CustomView : View +{ + protected override bool OnDrawingContent() + { + // Access driver capabilities through View.App + if (App?.Driver?.SupportsTrueColor == true) + { + // Use true color features + SetAttribute(new Attribute(Color.FromRgb(255, 0, 0), Color.FromRgb(0, 0, 255))); + } + else + { + // Fallback to 16-color mode + SetAttributeForRole(VisualRole.Normal); + } + + AddStr("Custom drawing with application context"); + return true; + } +} \ No newline at end of file diff --git a/docfx/docs/drivers.md b/docfx/docs/drivers.md index e8322ebf2..a7f523347 100644 --- a/docfx/docs/drivers.md +++ b/docfx/docs/drivers.md @@ -34,7 +34,7 @@ Application.Init(); // Method 2: Pass driver name to Init Application.Init(driverName: "unix"); -// Method 3: Pass a custom IConsoleDriver instance +// Method 3: Pass a custom IDriver instance var customDriver = new MyCustomDriver(); Application.Init(driver: customDriver); ``` @@ -56,7 +56,7 @@ The v2 driver architecture uses the **Component Factory** pattern to create plat Each driver is composed of specialized components, each with a single responsibility: -#### IConsoleInput<T> +#### IInput<T> Reads raw console input events from the terminal. The generic type `T` represents the platform-specific input type: - `ConsoleKeyInfo` for DotNetDriver and FakeDriver - `WindowsConsole.InputRecord` for WindowsDriver @@ -64,7 +64,7 @@ Reads raw console input events from the terminal. The generic type `T` represent Runs on a dedicated input thread to avoid blocking the UI. -#### IConsoleOutput +#### IOutput Renders the output buffer to the terminal. Handles: - Writing text and ANSI escape sequences - Setting cursor position @@ -88,8 +88,8 @@ Manages the screen buffer and drawing operations: #### IWindowSizeMonitor Detects terminal size changes and raises `SizeChanged` events when the terminal is resized. -#### ConsoleDriverFacade<T> -A unified facade that implements `IConsoleDriver` and coordinates all the components. This is what gets assigned to `Application.Driver`. +#### DriverFacade<T> +A unified facade that implements `IDriver` and coordinates all the components. This is what gets assigned to `Application.Driver`. ### Threading Model @@ -105,22 +105,22 @@ The driver architecture employs a **multi-threaded design** for optimal responsi ├──────────────────┬───────────────────┐ │ │ │ ┌────────▼────────┐ ┌──────▼─────────┐ ┌──────▼──────────┐ - │ Input Thread │ │ Main UI Thread│ │ ConsoleDriver │ + │ Input Thread │ │ Main UI Thread│ │ Driver │ │ │ │ │ │ Facade │ - │ IConsoleInput │ │ ApplicationMain│ │ │ + │ IInput │ │ ApplicationMain│ │ │ │ reads console │ │ Loop processes │ │ Coordinates all │ │ input async │ │ events, layout,│ │ components │ │ into queue │ │ and rendering │ │ │ └─────────────────┘ └────────────────┘ └─────────────────┘ ``` -- **Input Thread**: Started by `MainLoopCoordinator`, runs `IConsoleInput.Run()` which continuously reads console input and queues it into a thread-safe `ConcurrentQueue`. +- **Input Thread**: Started by `MainLoopCoordinator`, runs `IInput.Run()` which continuously reads console input and queues it into a thread-safe `ConcurrentQueue`. - **Main UI Thread**: Runs `ApplicationMainLoop.Iteration()` which: 1. Processes input from the queue via `IInputProcessor` 2. Executes timeout callbacks 3. Checks for UI changes (layout/drawing) - 4. Renders updates via `IConsoleOutput` + 4. Renders updates via `IOutput` This separation ensures that input is never lost and the UI remains responsive during intensive operations. @@ -131,25 +131,25 @@ When you call `Application.Init()`: 1. **ApplicationImpl.Init()** is invoked 2. Creates a `MainLoopCoordinator` with the appropriate `ComponentFactory` 3. **MainLoopCoordinator.StartAsync()** begins: - - Starts the input thread which creates `IConsoleInput` - - Initializes the main UI loop which creates `IConsoleOutput` - - Creates `ConsoleDriverFacade` and assigns to `Application.Driver` + - Starts the input thread which creates `IInput` + - Initializes the main UI loop which creates `IOutput` + - Creates `DriverFacade` and assigns to `IApplication.Driver` - Waits for both threads to be ready 4. Returns control to the application ### Shutdown Flow -When `Application.Shutdown()` is called: +When `IApplication.Shutdown()` is called: 1. Cancellation token is triggered 2. Input thread exits its read loop -3. `IConsoleOutput` is disposed +3. `IOutput` is disposed 4. Main thread waits for input thread to complete 5. All resources are cleaned up ## Component Interfaces -### IConsoleDriver +### IDriver The main driver interface that the framework uses internally. Provides: @@ -167,16 +167,6 @@ The main driver interface that the framework uses internally. Provides: - Use @Terminal.Gui.ViewBase.View.AddRune and @Terminal.Gui.ViewBase.View.AddStr for drawing - ViewBase infrastructure classes (in `Terminal.Gui/ViewBase/`) can access Driver when needed for framework implementation -### IConsoleDriverFacade - -Extended interface for v2 drivers that exposes the internal components: - -- `IInputProcessor InputProcessor` -- `IOutputBuffer OutputBuffer` -- `IWindowSizeMonitor WindowSizeMonitor` - -This interface allows advanced scenarios and testing. - ## Platform-Specific Details ### DotNetDriver (NetComponentFactory) @@ -219,79 +209,13 @@ This ensures Terminal.Gui applications can be debugged directly in Visual Studio - Uses `FakeConsole` for all operations - Allows injection of predefined input - Captures output for verification -- Always used when `Application._forceFakeConsole` is true - -## Example: Checking Driver Capabilities - -```csharp -Application.Init(); - -// The driver is internal - access through Application properties -// Check screen dimensions -var screenWidth = Application.Screen.Width; -var screenHeight = Application.Screen.Height; - -// Check if 24-bit color is supported -bool supportsTrueColor = Application.Driver?.SupportsTrueColor ?? false; - -// Access advanced components (for framework/infrastructure code only) -if (Application.Driver is IConsoleDriverFacade facade) -{ - // Access individual components for advanced scenarios - IInputProcessor inputProcessor = facade.InputProcessor; - IOutputBuffer outputBuffer = facade.OutputBuffer; - IWindowSizeMonitor sizeMonitor = facade.WindowSizeMonitor; - - // Use components for advanced scenarios - sizeMonitor.SizeChanging += (s, e) => - { - Console.WriteLine($"Terminal resized to {e.Size}"); - }; -} -``` +- Always used when `IApplication.ForceDriver` is `fake` **Important:** View subclasses should not access `Application.Driver`. Use the View APIs instead: - `View.Move(col, row)` for positioning - `View.AddRune()` and `View.AddStr()` for drawing -- `Application.Screen` for screen dimensions +- `View.App.Screen` for screen dimensions -## Custom Drivers - -To create a custom driver, implement `IComponentFactory`: - -```csharp -public class MyComponentFactory : ComponentFactory -{ - public override IConsoleInput CreateInput() - { - return new MyConsoleInput(); - } - - public override IConsoleOutput CreateOutput() - { - return new MyConsoleOutput(); - } - - public override IInputProcessor CreateInputProcessor( - ConcurrentQueue inputBuffer) - { - return new MyInputProcessor(inputBuffer); - } -} -``` - -Then use it: - -```csharp -ApplicationImpl.ChangeComponentFactory(new MyComponentFactory()); -Application.Init(); -``` - -## Legacy Drivers - -Terminal.Gui v1 drivers that implement `IConsoleDriver` but not `IConsoleDriverFacade` are still supported through a legacy compatibility layer. However, they do not benefit from the v2 architecture improvements (multi-threading, component separation, etc.). - -**Note**: The legacy `MainLoop` infrastructure (including the `MainLoop` class, `IMainLoopDriver` interface, and `FakeMainLoop`) has been removed in favor of the modern architecture. All drivers now use the `MainLoopCoordinator` and `ApplicationMainLoop` system exclusively. ## See Also diff --git a/docfx/docs/keyboard.md b/docfx/docs/keyboard.md index 14071f69c..6a08190fa 100644 --- a/docfx/docs/keyboard.md +++ b/docfx/docs/keyboard.md @@ -91,7 +91,7 @@ The Command can be invoked even if the `View` that defines them is not focused o ### **Key Events** -Keyboard events are retrieved from [Console Drivers](drivers.md) each iteration of the [Application](~/api/Terminal.Gui.App.Application.yml) [Main Loop](multitasking.md). The console driver raises the @Terminal.Gui.Drivers.ConsoleDriver.KeyDown and @Terminal.Gui.Drivers.ConsoleDriver.KeyUp events which invoke @Terminal.Gui.App.Application.RaiseKeyDownEvent* and @Terminal.Gui.App.Application.RaiseKeyUpEvent(Terminal.Gui.Input.Key) respectively. +Keyboard events are retrieved from [Drivers](drivers.md) each iteration of the [Application](~/api/Terminal.Gui.App.Application.yml) [Main Loop](multitasking.md). The driver raises the @Terminal.Gui.Drivers.IDriver.KeyDown and @Terminal.Gui.Drivers.IDriver.KeyUp events which invoke @Terminal.Gui.App.Application.RaiseKeyDownEvent* and @Terminal.Gui.App.Application.RaiseKeyUpEvent(Terminal.Gui.Input.Key) respectively. > [!NOTE] > Not all drivers/platforms support sensing distinct KeyUp events. These drivers will simulate KeyUp events by raising KeyUp after KeyDown. @@ -113,12 +113,12 @@ To define application key handling logic for an entire application in cases wher ## **Key Down/Up Events** -*Terminal.Gui* supports key up/down events with @Terminal.Gui.ViewBase.View.OnKeyDown* and @Terminal.Gui.ViewBase.View.OnKeyUp*, but not all [Console Drivers](drivers.md) do. To receive these key down and key up events, you must use a driver that supports them (e.g. `WindowsDriver`). +*Terminal.Gui* supports key up/down events with @Terminal.Gui.ViewBase.View.OnKeyDown* and @Terminal.Gui.ViewBase.View.OnKeyUp*, but not all [Drivers](drivers.md) do. To receive these key down and key up events, you must use a driver that supports them (e.g. `WindowsDriver`). # General input model -- Key Down and Up events are generated by `ConsoleDriver`. -- `Application` subscribes to `ConsoleDriver.Down/Up` events and forwards them to the most-focused `TopLevel` view using `View.NewKeyDownEvent` and `View.NewKeyUpEvent`. +- Key Down and Up events are generated by the driver. +- `IApplication` implementations subscribe to driver KeyDown/Up events and forwards them to the most-focused `TopLevel` view using `View.NewKeyDownEvent` and `View.NewKeyUpEvent`. - The base (`View`) implementation of `NewKeyDownEvent` follows a pattern of "Before", "During", and "After" processing: - **Before** - If `Enabled == false` that view should *never* see keyboard (or mouse input). @@ -134,25 +134,19 @@ To define application key handling logic for an entire application in cases wher - Subclasses of `View` can (rarely) override `OnKeyDown` (or subscribe to `KeyDown`) to see keys before they are processed - Subclasses of `View` can (often) override `OnKeyDownNotHandled` to do key processing for keys that were not previously handled. `TextField` and `TextView` are examples. -## ConsoleDriver - -* No concept of `Command` or `KeyBindings` -* Use the low-level `KeyCode` enum. -* Exposes non-cancelable `KeyDown/Up` events. The `OnKey/Down/Up` methods are public and can be used to simulate keyboard input (in addition to SendKeys). - ## Application * Implements support for `KeyBindingScope.Application`. * Keyboard functionality is now encapsulated in the @Terminal.Gui.App.IKeyboard interface, accessed via @Terminal.Gui.App.Application.Keyboard. * @Terminal.Gui.App.Application.Keyboard provides access to @Terminal.Gui.Input.KeyBindings, key binding configuration (QuitKey, ArrangeKey, navigation keys), and keyboard event handling. -* For backward compatibility, @Terminal.Gui.App.Application still exposes static properties/methods that delegate to @Terminal.Gui.App.Application.Keyboard (e.g., `Application.KeyBindings`, `Application.RaiseKeyDownEvent`, `Application.QuitKey`). +* For backward compatibility, @Terminal.Gui.App.Application still exposes static properties/methods that delegate to @Terminal.Gui.App.Application.Keyboard (e.g., `IApplication.KeyBindings`, `IApplication.RaiseKeyDownEvent`, `IApplication.QuitKey`). * Exposes cancelable `KeyDown/Up` events (via `Handled = true`). The `RaiseKeyDownEvent` and `RaiseKeyUpEvent` methods are public and can be used to simulate keyboard input. * The @Terminal.Gui.App.IKeyboard interface enables testability with isolated keyboard instances that don't depend on static Application state. ## View * Implements support for `KeyBindings` and `HotKeyBindings`. -* Exposes cancelable non-virtual methods for a new key event: `NewKeyDownEvent` and `NewKeyUpEvent`. These methods are called by `Application` can be called to simulate keyboard input. +* Exposes cancelable non-virtual methods for a new key event: `NewKeyDownEvent` and `NewKeyUpEvent`. These methods are called by `IApplication` can be called to simulate keyboard input. * Exposes cancelable virtual methods for a new key event: `OnKeyDown` and `OnKeyUp`. These methods are called by `NewKeyDownEvent` and `NewKeyUpEvent` and can be overridden to handle keyboard input. ## IKeyboard Architecture @@ -175,9 +169,9 @@ The @Terminal.Gui.App.IKeyboard interface provides a decoupled, testable archite ```csharp // Modern approach - using IKeyboard -Application.Keyboard.KeyBindings.Add(Key.F1, Command.HotKey); -Application.Keyboard.RaiseKeyDownEvent(Key.Enter); -Application.Keyboard.QuitKey = Key.Q.WithCtrl; +App.Keyboard.KeyBindings.Add(Key.F1, Command.HotKey); +App.Keyboard.RaiseKeyDownEvent(Key.Enter); +App.Keyboard.QuitKey = Key.Q.WithCtrl; // Legacy approach - still works (delegates to Application.Keyboard) Application.KeyBindings.Add(Key.F1, Command.HotKey); @@ -202,6 +196,24 @@ Assert.Equal(Key.Q.WithCtrl, keyboard1.QuitKey); Assert.Equal(Key.X.WithCtrl, keyboard2.QuitKey); ``` +**Accessing application context from views:** + +```csharp +public class MyView : View +{ + protected override bool OnKeyDown(Key key) + { + // Use View.App instead of static Application + if (key == Key.F1) + { + App?.Keyboard?.KeyBindings.Add(Key.F2, Command.Accept); + return true; + } + return base.OnKeyDown(key); + } +} +``` + ### Architecture Benefits - **Parallel Testing**: Multiple test methods can create and use separate @Terminal.Gui.App.IKeyboard instances simultaneously without state interference. @@ -218,4 +230,10 @@ The @Terminal.Gui.App.Keyboard class implements @Terminal.Gui.App.IKeyboard and - **Events**: KeyDown, KeyUp events for application-level keyboard monitoring - **Command Implementations**: Handlers for Application-scoped commands (Quit, Suspend, Navigation, Refresh, Arrange) -The @Terminal.Gui.App.ApplicationImpl class creates and manages the @Terminal.Gui.App.IKeyboard instance, setting its `Application` property to `this` to provide the necessary @Terminal.Gui.App.IApplication reference. \ No newline at end of file +The @Terminal.Gui.App.ApplicationImpl class creates and manages the @Terminal.Gui.App.IKeyboard instance, setting its `IApplication` property to `this` to provide the necessary @Terminal.Gui.App.IApplication reference. + +## Driver + +* No concept of `Command` or `KeyBindings` +* Use the low-level `KeyCode` enum. +* Exposes non-cancelable `KeyDown/Up` events. The `OnKey/Down/Up` methods are public and can be used to simulate keyboard input (in addition to SendKeys) \ No newline at end of file diff --git a/docfx/docs/migratingfromv1.md b/docfx/docs/migratingfromv1.md index fbf121c2f..8a459e0a0 100644 --- a/docfx/docs/migratingfromv1.md +++ b/docfx/docs/migratingfromv1.md @@ -85,12 +85,12 @@ When measuring the screen space taken up by a `string` you can use the extension In v1, @Terminal.Gui.View was derived from `Responder` which supported `IDisposable`. In v2, `Responder` has been removed and @Terminal.Gui.View is the base-class supporting `IDisposable`. -In v1, @Terminal.Gui./Terminal.Gui.Application.Init) automatically created a toplevel view and set [Application.Top](~/api/Terminal.Gui.Application.Top. In v2, @Terminal.Gui.App.Application.Init no longer automatically creates a toplevel or sets @Terminal.Gui.App.Application.Top; app developers must explicitly create the toplevel view and pass it to @Terminal.Gui.App.Application.Run (or use `Application.Run`). Developers are responsible for calling `Dispose` on any toplevel they create before exiting. +In v1, @Terminal.Gui./Terminal.Gui.Application.Init) automatically created a toplevel view and set [Application.Current](~/api/Terminal.Gui.Application.Current. In v2, @Terminal.Gui.App.Application.Init no longer automatically creates a toplevel or sets @Terminal.Gui.App.Application.Current; app developers must explicitly create the toplevel view and pass it to @Terminal.Gui.App.Application.Run (or use `Application.Run`). Developers are responsible for calling `Dispose` on any toplevel they create before exiting. ### How to Fix * Replace `Responder` with @Terminal.Gui.View -* Update any code that assumes `Application.Init` automatically created a toplevel view and set `Application.Top`. +* Update any code that assumes `Application.Init` automatically created a toplevel view and set `Application.Current`. * Update any code that assumes `Application.Init` automatically disposed of the toplevel view when the application exited. ## @Terminal.Gui.Pos and @Terminal.Gui.Dim types now adhere to standard C# idioms @@ -523,6 +523,6 @@ new ( * To simplify programming, any `View` added as a SubView another `View` will have it's lifecycle owned by the Superview; when a `View` is disposed, it will call `Dispose` on all the items in the `SubViews` property. Note this behavior is the same as it was in v1, just clarified. -* In v1, `Application.End` called `Dispose ()` on @Terminal.Gui.App.Application.Top (via `Runstate.Toplevel`). This was incorrect as it meant that after `Application.Run` returned, `Application.Top` had been disposed, and any code that wanted to interrogate the results of `Run` by accessing `Application.Top` only worked by accident. This is because GC had not actually happened; if it had the application would have crashed. In v2 `Application.End` does NOT call `Dispose`, and it is the caller to `Application.Run` who is responsible for disposing the `Toplevel` that was either passed to `Application.Run (View)` or created by `Application.Run ()`. +* In v1, `Application.End` called `Dispose ()` on @Terminal.Gui.App.Application.Current (via `Runstate.Toplevel`). This was incorrect as it meant that after `Application.Run` returned, `Application.Current` had been disposed, and any code that wanted to interrogate the results of `Run` by accessing `Application.Current` only worked by accident. This is because GC had not actually happened; if it had the application would have crashed. In v2 `Application.End` does NOT call `Dispose`, and it is the caller to `Application.Run` who is responsible for disposing the `Toplevel` that was either passed to `Application.Run (View)` or created by `Application.Run ()`. * Any code that creates a `Toplevel`, either by using `top = new()` or by calling either `top = Application.Run ()` or `top = ApplicationRun()` must call `top.Dispose` when complete. The exception to this is if `top` is passed to `myView.Add(top)` making it a subview of `myView`. This is because the semantics of `Add` are that the `myView` takes over responsibility for the subviews lifetimes. Of course, if someone calls `myView.Remove(top)` to remove said subview, they then re-take responsbility for `top`'s lifetime and they must call `top.Dispose`. diff --git a/docfx/docs/mouse.md b/docfx/docs/mouse.md index 552a6c585..56e8dff85 100644 --- a/docfx/docs/mouse.md +++ b/docfx/docs/mouse.md @@ -66,14 +66,14 @@ Here are some common mouse binding patterns used throughout Terminal.Gui: At the core of *Terminal.Gui*'s mouse API is the @Terminal.Gui.Input.MouseEventArgs class. The @Terminal.Gui.Input.MouseEventArgs class provides a platform-independent abstraction for common mouse events. Every mouse event can be fully described in a @Terminal.Gui.Input.MouseEventArgs instance, and most of the mouse-related APIs are simply helper functions for decoding a @Terminal.Gui.Input.MouseEventArgs. -When the user does something with the mouse, the `ConsoleDriver` maps the platform-specific mouse event into a `MouseEventArgs` and calls `Application.RaiseMouseEvent`. Then, `Application.RaiseMouseEvent` determines which `View` the event should go to. The `View.OnMouseEvent` method can be overridden or the `View.MouseEvent` event can be subscribed to, to handle the low-level mouse event. If the low-level event is not handled by a view, `Application` will then call the appropriate high-level helper APIs. For example, if the user double-clicks the mouse, `View.OnMouseClick` will be called/`View.MouseClick` will be raised with the event arguments indicating which mouse button was double-clicked. +When the user does something with the mouse, the driver maps the platform-specific mouse event into a `MouseEventArgs` and calls `IApplication.Mouse.RaiseMouseEvent`. Then, `IApplication.Mouse.RaiseMouseEvent` determines which `View` the event should go to. The `View.OnMouseEvent` method can be overridden or the `View.MouseEvent` event can be subscribed to, to handle the low-level mouse event. If the low-level event is not handled by a view, `IApplication` will then call the appropriate high-level helper APIs. For example, if the user double-clicks the mouse, `View.OnMouseClick` will be called/`View.MouseClick` will be raised with the event arguments indicating which mouse button was double-clicked. ### Mouse Event Processing Flow Mouse events are processed through the following workflow using the [Cancellable Work Pattern](cancellable-work-pattern.md): -1. **Driver Level**: The ConsoleDriver captures platform-specific mouse events and converts them to `MouseEventArgs` -2. **Application Level**: `Application.RaiseMouseEvent` determines the target view and routes the event +1. **Driver Level**: The driver captures platform-specific mouse events and converts them to `MouseEventArgs` +2. **Application Level**: `IApplication.Mouse.RaiseMouseEvent` determines the target view and routes the event 3. **View Level**: The target view processes the event through: - `OnMouseEvent` (virtual method that can be overridden) - `MouseEvent` event (for event subscribers) @@ -157,8 +157,8 @@ view.MouseStateChanged += (sender, e) => The @Terminal.Gui.App.Application.MouseEvent event can be used if an application wishes to receive all mouse events before they are processed by individual views: -```cs -Application.MouseEvent += (sender, e) => +```csharp +App.Mouse.MouseEvent += (sender, e) => { // Handle application-wide mouse events if (e.Flags.HasFlag(MouseFlags.Button3Clicked)) @@ -169,6 +169,24 @@ Application.MouseEvent += (sender, e) => }; ``` +For view-specific mouse handling that needs access to application context, use `View.App`: + +```csharp +public class MyView : View +{ + protected override bool OnMouseEvent(MouseEventArgs mouseEvent) + { + if (mouseEvent.Flags.HasFlag(MouseFlags.Button3Clicked)) + { + // Access application mouse functionality through View.App + App?.MouseEvent?.Invoke(this, mouseEvent); + return true; + } + return base.OnMouseEvent(mouseEvent); + } +} +``` + ## Mouse Enter/Leave Events The @Terminal.Gui.ViewBase.View.MouseEnter and @Terminal.Gui.ViewBase.View.MouseLeave events enable a View to take action when the mouse enters or exits the view boundary. Internally, this is used to enable @Terminal.Gui.ViewBase.View.Highlight functionality: @@ -218,3 +236,4 @@ The `MouseEventArgs` provides both coordinate systems: + diff --git a/docfx/docs/navigation.md b/docfx/docs/navigation.md index c83f72d50..7fde06795 100644 --- a/docfx/docs/navigation.md +++ b/docfx/docs/navigation.md @@ -374,7 +374,7 @@ In v1 `View` had `MostFocused` property that traversed up the view-hierarchy ret var focused = Application.Navigation.GetFocused(); // This replaces the v1 pattern: -// var focused = Application.Top.MostFocused; +// var focused = Application.Current.MostFocused; ``` ## How Does `View.Add/Remove` Work? diff --git a/docfx/docs/toc.yml b/docfx/docs/toc.yml index 66c9b5cbb..8acb4573f 100644 --- a/docfx/docs/toc.yml +++ b/docfx/docs/toc.yml @@ -26,6 +26,8 @@ href: events.md - name: Lexicon & Taxonomy href: lexicon.md +- name: Terminology Proposal + href: terminology-index.md - name: Keyboard href: keyboard.md - name: Layout Engine diff --git a/docfx/schemas/tui-config-schema.json b/docfx/schemas/tui-config-schema.json index 87fec828d..52b7d7f38 100644 --- a/docfx/schemas/tui-config-schema.json +++ b/docfx/schemas/tui-config-schema.json @@ -220,7 +220,7 @@ "additionalProperties": true, "definitions": { "Color": { - "description": "One be either one of the W3C standard color names (parsed case-insensitively), a ColorName16 (e.g. 'BrightBlue', parsed case-insensitively), an rgb(r,g,b) tuple, or a hex color string in the format #RRGGBB.", + "description": "One of the standard color names (parsed case-insensitively; (e.g. 'BrightBlue'), an rgb(r,g,b) tuple, or a hex color string in the format #RRGGBB.", "$schema": "http://json-schema.org/draft-07/schema#", "type": "string", "oneOf": [