Update copilot references

This commit is contained in:
vczh
2026-02-03 16:13:37 -08:00
parent 8d0dcb17e3
commit e4684b17b2
31 changed files with 661 additions and 132 deletions
+13
View File
@@ -830,3 +830,16 @@ It provides a comprehensive testing framework, XML-to-C++ compilation, and integ
- [TAB and Control Focus](./manual/gacui/advanced/tab.md)
- [Creating New List Controls](./manual/gacui/advanced/impllistcontrol.md)
- [Porting GacUI to Other Platforms](./manual/gacui/advanced/implosprovider.md)
- [Hosted Mode and Remote Protocol](./manual/gacui/modes/home.md)
- [Remote Protocol Core Application](./manual/gacui/modes/remote_core.md)
- [Remote Protocol Client Application](./manual/gacui/modes/remote_client.md)
- [Implementing a Communication Protocol](./manual/gacui/modes/remote_communication.md)
## Unit Testing
- [Using Unit Test Framework](./manual/unittest/vlpp.md)
- [Running GacUI in Unit Test Framework](./manual/unittest/gacui.md)
- [Snapshots and Frames](./manual/unittest/gacui_frame.md)
- [IO Interaction](./manual/unittest/gacui_io.md)
- [Accessing Controls](./manual/unittest/gacui_controls.md)
- [Snapshot Viewer](./manual/unittest/gacui_snapshots.md)
@@ -1,6 +1,6 @@
# Application
A GacUI application requires a user defined function **void GuiMain(void)**. In order to start a GacUI application, one of the following functions must be called in entry function. - For Windows, One of the following functions must be called in **WinMain** to start the application: - SetupWindowsDirect2DRenderer - SetupHostedWindowsDirect2DRenderer - SetupWindowsGDIRenderer - SetupHostedWindowsGDIRenderer They initialize necessary objects and call **GuiMain** with different rendering techniques. - For macOS, **SetupOSXCoreGraphicsRenderer** must be called in **main**. - For Linux, **SetupGGacRenderer** must be called in **main**.
A GacUI application requires a user defined function **void GuiMain(void)**. In order to start a GacUI application, one of the following functions must be called in entry function. - For Windows, One of the following functions must be called in **WinMain** to start the application: - SetupWindowsDirect2DRenderer - SetupHostedWindowsDirect2DRenderer - SetupWindowsGDIRenderer - SetupHostedWindowsGDIRenderer They initialize necessary objects and call **GuiMain** with different rendering techniques. - For macOS, **SetupOSXCoreGraphicsRenderer** must be called in **main**. - For Linux Wayland, **SetupWGacRenderer** must be called in **main**. - For Linux XWindow, **SetupGGacRenderer** must be called in **main**.
When **GuiMain** is called, the **GuiApplication** object is ready, which can be accessed by the **GetApplication** function. A typical **GuiMain** function looks like: ``` void GuiMain() { // 1. create a window, anything that inherits from GuiWindow works MyWindow myWindow; // 2. decide where to show the window, it could be any location in any monitor you like myWindow.MoveToScreenCenter() // 3. show the window as a main window of the application GetApplication()->Run(&myWindow); // 4. when the main window is closed, it gets here } ```
@@ -20,3 +20,11 @@ For example, when you put a button in a tab header to remove the tab page, you c
**RunGuiTask** works like **InvokeInMainThreadAndWait**, but it is OK to call in the UI thread. It will return only after the task is completed.
**SetupWindowsGDIRendererInternal** and **SetupHostedWindowsDirect2DRenderer** setup GacUI with only one OS native window. All non-main windows will be renderered inside that window.
**SetupRemoteNativeController** setup GacUI with remote protocol, it is also running in hosted mode. It takes an argument to an implementation of **IGuiRemoteProtocol**, which is responsible to serialize and deserialize commands between core and renderer.
**SetupRawWindowsGDIRenderer** and **SetupRawWindowsDirect2DRenderer** starts up a GacUI application with **INativeController** only, there will be no **GuiApplication** running and therefore no controls can be used. This is useful when you want to run a remote protocol renderer that interacts with a user. **GuiRemoteRendererSingle** connects to one actual GacUI application that running with remote protoco. **IMPORTANT:** you must implement the communicating by yourself. GacUI has a default implementation of remote protocol serialization with JSON or string format. But you must implement the sending and receiving.
When running GacUI with unit test framework, it starts GacUI with remote protocol, with an implementation of **IGuiRemoteProtocol** that logs the whole UI to multiple JSON files. **UnitTestSnapshotViewerAppWindow** and **UnitTestSnapshotViewerViewModel** offers an ability to render these JSON files with limited user interaction to inspect what happened during the unit tests. In order to ensure a test case creates exactly the same logging across multiple platforms, text measurments are fake and limited to simple layout. Therefore in the snapshot viewer you might find controls for displaying texts (e.g. a button) appear to be larger than expected, that is normal.
@@ -36,9 +36,13 @@ For graphics API like DirectX, sometimes the device context will be invalid and
**INativeController** consists of multiple services: - **INativeCallbackService**: Managing **INativeControllerListener** objects so that different components can receive system messages and keep the code decoupled. - **INativeAsyncService**: Managing and executing task queues in multiple threads. - **INativeResourceService**: Accessing cursors and fonts from the OS. - **INativeClipboardService**: Accessing clipboard from the OS. - **INativeScreenService**: Accessing physical monitors from the OS. - **INativeDialogService**: Accessing default dialogs from the OS. - **INativeImageService**: Loading image from files or memories. - **INativeWindowService**: Managing windows using OS's window manager. - **INativeInputService**: Accessing global keyboard status and timer.
## Hosted Mode
**GuiHostedController** and **SetHostedApplication** can create a hosted mode implementation of **INativeController** on top of a native **INativeController** instance. Checkout **SetupWindowsGDIRendererInternal** and **SetupHostedWindowsDirect2DRenderer** for details. When running GacUI with remote protocol, **SetupRemoteNativeController** will create **GuiHostedController** on top of **GuiRemoteController**.
When an **INativeController** is running as a native OS provider to **GuiHostedController**, it has to handle extra works for rendering in **IGuiGraphicsRenderTarget**: - **IsInHostedRendering()** will be called to detect if the render target is working. - **StartHostedRendering()**. This is called only under hosted mode, so that all subsequent rendering calls are hinted. - Multiple **StartRendering()** and **StopRendering()** calls sent from the window. - **StopHostedRendering()**, if any **StopRendering()** fails, this should fail with the same reason.**GuiGraphicsRenderTarget** can be used to simplifies the implementation, therefore only one call to **StartRenderingOnNativeWindow** and **StopRenderingOnNativeWindow** is sent to the render target.
## Development Note
Implementing **INativeController** is a super complex topic. In order to port GacUI to a new platform, you must know everything about how GacUI interact with the OS. It is highly recommended to read the [Windows](https://github.com/vczh-libraries/GacUI/tree/master/Source/NativeWindow/Windows), [macOS](https://github.com/vczh-libraries/iGac) and [Linux](https://github.com/vczh-libraries/gGac) implementation for **INativeController** before creating yours. Especially there are GDI and Direct2D support in Windows, it is a good example to know how to allow multiple rendering techniques in one OS.
In the future, **INativeHostedController** and **INativeHostedWindow** will be created for scenarios that all GacUI windows are rendered in one OS native window, like CLI, UWP or browsers.
Implementing **INativeController** is a super complex topic. In order to port GacUI to a new platform, you must know everything about how GacUI interact with the OS. It is highly recommended to read the [Windows](https://github.com/vczh-libraries/GacUI/tree/master/Source/NativeWindow/Windows), [macOS](https://github.com/vczh-libraries/iGac), [Linux Wayland](https://github.com/vczh-libraries/wGac) and [Linux XWindow](https://github.com/vczh-libraries/gGac) implementation for **INativeController** before creating yours. Especially there are GDI and Direct2D support in Windows, it is a good example to know how to allow multiple rendering techniques in one OS.
@@ -0,0 +1,14 @@
# Hosted Mode and Remote Protocol
Using **SetupHostedWindowsGDIRenderer** or **SetupHostedWindowsDirect2DRenderer** instead of **SetupWindowsGDIRenderer** or **SetupWindowsDirect2DRenderer** runs a GacUI application in hosted mode. A hosted mode GacUI application will start only one OS native window, other windows are rendered inside it virtually. System dialogs will be replaced by predefined GacUI implemented dialogs by default, so that anything will be strictly inside the OS native window.
Using **SetupRemoteNativeController** runs a GacUI application with remote protocol (forced in hosted mode), which becomes headless, instead of render anything on the screen, it sends out rendering commands to a remote client. This part will be covered in [Remote Protocol Core Application](../.././gacui/modes/remote_core.md).
If you want a remote protocol renderer that still work with other components of GacUI, **GuiRemoteRendererSingle** implements everything that the remote protocol needs and starts a window, displaying anything from the core GacUI application, with user interactions covered. This part will be covered in [Remote Protocol Client Application](../.././gacui/modes/remote_client.md).
The [GacJS](https://github.com/vczh-libraries/GacJS) also implements a GacUI renderer in a browser, connecting to a GacUI application via HTTP protocol. But the HTTP protocol implementation is not official, it is for demo only, no security guarantee is provided.
GacUI does not provide any protocol implementation, you need to connect the GacUI application with a remote client by yourself. And you are free to go with and not limited to anything below: - Network protocols such as TCP, HTTP, HTTPS, NamedPipe, etc. For HTTP and NamedPipd, the **GacUI** repo has a test client as a demo. - Non-networking protocols such as Foreign Function Interface (FFI) or even Dynamic-Linked Library (DLL). But GacUI still offers some wrappers that simplifies everything to string sending and receiving. This part will be covered in [Implementing a Communication Protocol](../.././gacui/modes/remote_communication.md).
In [this folder](https://github.com/vczh-libraries/Release/tree/master/Import/Metadata) there are definitions of the remote protocol in JSON format and in its original format. The original format can be parsed using **vl::presentation::remoteprotocol::Parser** in **GacUICompiler.h**. You can then write any code generation tool to generate code in any programming language you want.
File diff suppressed because one or more lines are too long
@@ -0,0 +1,14 @@
# Implementing a Communication Protocol
**IGuiRemoteProtocol** has a lot of strong typed messages defined as functions. When the core application wants to send anything to a remote client, a member function is called. When the remote client sent anything to the core application, a member function in **IGuiRemoteProtocol::GetRemoteEventProcessor** is called. Some messages need to wait for a response from the remote client, the first argument of such message is always **vint id**. The core application will be blocked until the remote client sends back a expected response message with the same id.
Therefore **IGuiRemoteProtocol** represents a renderer. The core side implementation sends messages to client and receive responses from client. The client side implementation receives messages from core and send responses to core.
**IGuiRemoteProtocolChannel\<T\>** and **IGuiRemoteProtocolChannelReceiver\<T\>** works like **IGuiRemoteProtocol** and **IGuiRemoteEventProcessor**, but there is no strong typed messages anymore, everything becomes the **T**.
**GuiRemoteProtocolFromJsonChannel** (for core) and **GuiRemoteJsonChannelFromProtocol** (for client) handles message translation between **IGuiRemoteProtocol** and **IGuiRemoteProtocolChannel\<Ptr\<JsonObject\>\>**.
**GuiRemoteJsonChannelStringSerializer** (for core) and **GuiRemoteJsonChannelStringDeserializer** (for client) handles message translation between **IGuiRemoteProtocolChannel\<Ptr\<JsonObject\>\>** and **IGuiRemoteProtocolChannel\<WString\>**.
Although message are sent bidirectionally, but the naming is saying what is done from core to client. [Remote Protocol Client Application](../.././gacui/modes/remote_client.md) and [Implementing a Communication Protocol](../.././gacui/modes/remote_communication.md) demos how to setup the pipeline from **IGuiRemoteProtocol** all the way to sending and receiving strings. You can also implements your own pipeline using different message formats. JSON serialization is offered in GacUI by default, but you can also code generate your own serializer with other underlying format using metadata in [this folder](https://github.com/vczh-libraries/Release/tree/master/Import/Metadata).
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1,24 @@
# Accessing Controls
When a window or a custom control is created from GacUI XML Resource, and you need the pointer to a specific sub object, you don't have to **public** them. Here are functions to access a specific control, composition or component by its **ref.Name** attribute, or to access a control by its text, defined in the **vl::presentation::controls** namespace.
## FindObjectByName(rootObject, name)
The first parameter must be an object that created by an \<Instance/\>, e.g. a window or a custom control. The second parameter must be an object that directly inside that particular \<Instance/\> with **ref.Name** attribute assigned. If nothing is found, the function crashes. ```c++ auto button = FindObjectByName(window, L"buttonSomething"); ```
## TryFindObjectByName(rootObject, name)
Same as **FindObjectByName**, but it returns null if nothing is found.
## FindObjectByName(rootObject, name1, name2, ...)
This is a shortcut of a chain call: to **... FindObjectByName(FindObjectByName(rootObject, name1), name2) ...**. It is useful when your target object is inside nested custom controls in a window, you need to provide all **ref.Name** attributes in the hierarchy. **IMPORTANT:** such custom controls must be created by \<Instance/\>.
## FindControlByText\<Type\>(control, text)
The first parameter must be a control. The is the text of the target sub control. If nothing is found matching the type and the text, the function crashes. Otherwise it would return a random one within all matching controls. Unlike **FindObjectByName**, you can use this function to search into nested custom controls. ```c++ auto button = FindControlByText<GuiButton>(window, L"Click Me!"); ```
## TryFindControlByText\<Type\>(control, text)
Same as **FindControlByText**, but it returns null if nothing is found.
@@ -0,0 +1,14 @@
# Snapshots and Frames
If you let the unit test framework create a window from a GacUI XML resource, You can set the 4th parametero of **GacUIUnitTest_StartFast_WithResourceAsText** to offer some callbacks. The type is **GacUIUnitTest_Installer** and it has these members: - **Func\<void()\> initialize;** it is called before the window is created. - **Func\<void(GuiWindow*)\> installWindow;** it is called before the window is opened. - **Func\<void()\> finalize;** it is called after the window is closed. - **Nullable\<UnitTestScreenConfig\> config;** more configurations, including font list, screen size, etc.
A test case typically begins with a call to **GacUIUnitTest_SetGuiMainProxy** with multiple frames created. You are recommended to follow the rule: - The name of the first frame should be **Ready**. - Names of other frames describe what has been done in the previous frame. - In each frame visible UI changes must be triggered. - As each IO operation is completed after all events are handled in the GacUI application, if an operation calls any blocking function, wrap it in **InvokeInMainThread**. - When an action is taken, if the side effect happens immediately, assertion should be placed right after the action. - When an action is taken, if the side effect happens async but fast, assertion should be placed at the beginning of the next frame. - Whenever possible keep the last frame only calling the **window-\>Close();** function. This rule could sometimes conflict with the previous rule, the previous rule has higher priority.
## About Visible UI Change
A visible UI change means something that cause GacUI to refresh any window in your application. This includes changing texts, sizes, layouts; showing or hiding windows or controls; enabling or disabling controls; select anything or type anything in a control; etc. The unit test framework will detect whether any visible UI change has been made in a frame, and the test fail if there is not. **IMPORTANT:** DO NOT make a frame just for assertion as it doesn't cause any visible UI change.
## About Blocking Function
Blocking functions are anything that does not return but the application is still running. For example, **ShowDialog**, **ShowModal**, **ShowModalAndDelete**, **InvokeInMainThreadAndWait**, etc. Any IO interaction inside a frame only returns after GacUI handles all events. For example, **protocol-\>LClick(location);** on a button only returns after **Clicked** as well as all other mouse events are handled. If in any of this event a blocking function is called, **LClick** blocks the current frame, and the unit test framework detects such action and fail the test. In this case, call **LClick** in **InvokeInMainThread** instead. ```c++ GetApplication()->InvokeInMainThread(window, [=]() { protocol->LClick(location); }); ```
@@ -0,0 +1,16 @@
# IO Interaction
To calculate the location of a control or a composition for a following mouse interaction, use **protocol-\>LocationOf(controlOrComposition, ratioX?, ratioY?, offsetX?, offsetY?)**. It calculates the global location within an object using **left + width * ratioX + offsetX** and **top + height * ratioY + offsetY**. You can omit the last 4 parameters to get the center of the object. Such location can be passed to mouse interaction functions directly.
## Mouse Interactions
Here are a list of available mouse interaction functions in the **protocol** object: - **MouseMove(location)**: move the mouse to the specified location - **_LDown(location?)**: trigger a left button down event. - **_LUp(location?)**: trigger a left button up event. - **_LDBClick(location?)**: trigger a left button double click event. - **LClick(location?) and LClick(location, ctrl, shift, alt)**: trigger multiple events to simulate a click with optional modifier keys. - **LDBClick(location?) and LDBClick(location, ctrl, shift, alt)**: trigger multiple events to simulate a double click with optional modifier keys. - M (middle button) version and R (right button) version of above functions.
## Mouse Wheel Interactions
Here are a list of available mouse wheel interaction functions in the **protocol** object: - **_Wheel(up, position?) and _Wheel(up, position, ctrl, shift, alt)**: scroll the wheel forward. A negative number means backard. **WheelDown** or **WheelUp** for shortcuts to scroll with jumps, a jump scales to 120. - **_HWheel(right, position?) and _HWheel(right, position, ctrl, shift, alt)**: scroll the horizontal wheel towards right. A negative number means towards left. **HWheelLeft** or **HWheelRight** for shortcuts to scroll with jumps, a jump scales to 120.
## Key Interactions
Here are a list of available key interaction functions in the **protocol** object: - **_KeyDown(key)**: trigger a key down event. - **_KeyDownRepeat(key)**: trigger a key down event triggers by keep pressing a key. - **_KeyUp(key)**: trigger a key up event. - **KeyPress(key) and KeyPress(key, ctrl, shift, alt)**: trigger multiple events to simulate a key press with optional modifier keys. - **TypeString(string)**: trigger a sequence of char events to type a string. Key events will not be triggered.
@@ -0,0 +1,4 @@
# Snapshot Viewer
After running the GacUI unit test, snapshots will be stored in a folder specified by **UnitTestFrameworkConfig::snapshotFolder**, which is passed to **GacUIUnitTest_Initialize** in the **main** function. You can write your own snapshot viewer to render any snapshot from a test run like this. ```c++ #include <GacUI.UnitTest.UI.h> #include <Windows.h> using namespace vl; using namespace vl::filesystem; using namespace vl::presentation; using namespace vl::presentation::controls; using namespace vl::presentation::unittest; using namespace gaclib_controls; void GuiMain() { theme::RegisterTheme(Ptr(new darkskin::Theme)); { // you can use GetApplication()->GetExecutablePath() if hardcoding a relative path to the folder contains the executable is possible. FilePath snapshotFolderPath = ...; UnitTestSnapshotViewerAppWindow window(Ptr(new UnitTestSnapshotViewerViewModel(snapshotFolderPath))); window.ForceCalculateSizeImmediately(); window.MoveToScreenCenter(); window.WindowOpened.AttachLambda([&](auto&&...) { window.ShowMaximized(); }); GetApplication()->Run(&window); } } int CALLBACK WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int CmdShow) { return SetupWindowsDirect2DRenderer(); } ``` **UnitTestSnapshotViewerAppWindow** and **UnitTestSnapshotViewerViewModel** are defined in **GacUI.UnitTest.UI**.
@@ -0,0 +1,12 @@
# Using Unit Test Framework
The most simple way to start the unit test framework would be: ```c++ #include <Vlpp.h> using namespace vl; using namespace vl::unittest; #ifdef VCZH_MSVC int wmain(int argc , wchar_t* argv[]) #else int main(int argc, char** argv) #endif { int result = unittest::UnitTest::RunAndDisposeTests(argc, argv); vl::FinalizeGlobalStorage(); #ifdef VCZH_CHECK_MEMORY_LEAKS _CrtDumpMemoryLeaks(); #endif return result; } ```
In this way you could pass **/D**, **/R**, **/C** and **/F** directly to your unit test binary and they will be handled by the unit test framework properly.
You can create multiple cpp files for your test cases, **/F** will automatically react to them. A typical test file would be: ```c++ #include <Vlpp.h> using namespace vl; using namespace vl::unittest; void AnError() { CHECK_FAIL(L"Not Implemented!"); } void AnException() { throw Exception(L"A message"); } TEST_FILE { TEST_CASE(L"This is a test case") { TEST_ASSERT(1 + 1 == 2); }); TEST_CATEGORY(L"This is a test category") { TEST_CASE(L"This is another test case") { TEST_ERROR(AnError()); TEST_EXCEPTION(AnException(), Exception, [](const Exception& e) { TEST_ASSERT(e.Message() == L"A message"); }); }); }); } ``` - **TEST_ASSERT(condition);** accepts an expression and fails if it evaluates to false. - **TEST_ERROR(statement);** accepts a statement and fails if it does not throw an exception is or inheriting from **Error**. Such error are usually triggered by **CHECK_ERROR** and **CHECK_FAIL**. - **TEST_EXCEPTION(statement,exception,handler);** executes the statement, fails if it does not throw an exception is or inheriting from **exception**. The handler will be called on that exception for additional assertion. Please be aware that these are macros, commas may be interepreted unexpectedly.
**TEST_CATEGORY** could be used to group other **TEST_CATEGORY** and **TEST_CASE**. **TEST_ASSERT**, **TEST_ERROR** and **TEST_EXCEPTION** can only be used in **TEST_CASE**. **TEST_CASE_ASSERT(condition);** is a shortcut for a **TEST_CASE** containing only a **TEST_ASSERT**.
If you get **TestA.cpp**, **TestB.cpp** and **TestC.cpp**, using no **/F** will run tests in all three files, using **/F:Test*.cpp** will run tests in all three files, using **/F:TestA.cpp /F:TestB.cpp** will run tests in **TestA.cpp** and **TestB.cpp** only.