diff options
-rw-r--r-- | .gitattributes | 2 | ||||
-rw-r--r-- | .gitignore | 324 | ||||
-rw-r--r-- | CruUI.sln | 31 | ||||
-rw-r--r-- | CruUI/CruUI.vcxproj | 183 | ||||
-rw-r--r-- | CruUI/CruUI.vcxproj.filters | 90 | ||||
-rw-r--r-- | CruUI/application.cpp | 96 | ||||
-rw-r--r-- | CruUI/application.h | 75 | ||||
-rw-r--r-- | CruUI/base.h | 42 | ||||
-rw-r--r-- | CruUI/cru_event.cpp | 5 | ||||
-rw-r--r-- | CruUI/cru_event.h | 86 | ||||
-rw-r--r-- | CruUI/exception.cpp | 29 | ||||
-rw-r--r-- | CruUI/exception.h | 42 | ||||
-rw-r--r-- | CruUI/global_macros.h | 3 | ||||
-rw-r--r-- | CruUI/graph/graph.cpp | 233 | ||||
-rw-r--r-- | CruUI/graph/graph.h | 126 | ||||
-rw-r--r-- | CruUI/main.cpp | bin | 0 -> 3038 bytes | |||
-rw-r--r-- | CruUI/system_headers.h | 20 | ||||
-rw-r--r-- | CruUI/timer.cpp | 97 | ||||
-rw-r--r-- | CruUI/timer.h | 49 | ||||
-rw-r--r-- | CruUI/ui/control.cpp | 488 | ||||
-rw-r--r-- | CruUI/ui/control.h | 248 | ||||
-rw-r--r-- | CruUI/ui/events/ui_event.cpp | 19 | ||||
-rw-r--r-- | CruUI/ui/events/ui_event.h | 180 | ||||
-rw-r--r-- | CruUI/ui/layout_base.h | 96 | ||||
-rw-r--r-- | CruUI/ui/ui_base.cpp | 8 | ||||
-rw-r--r-- | CruUI/ui/ui_base.h | 105 | ||||
-rw-r--r-- | CruUI/ui/window.cpp | 545 | ||||
-rw-r--r-- | CruUI/ui/window.h | 257 | ||||
-rw-r--r-- | LICENSE | 201 | ||||
-rw-r--r-- | Layout Rules.md | 95 |
30 files changed, 3775 insertions, 0 deletions
diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000..dfe07704 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +# Auto detect text files and perform LF normalization +* text=auto diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..219a825e --- /dev/null +++ b/.gitignore @@ -0,0 +1,324 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. +## +## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore + +# User-specific files +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ + +# Visual Studio 2015/2017 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# Visual Studio 2017 auto generated files +Generated\ Files/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUNIT +*.VisualState.xml +TestResult.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# Benchmark Results +BenchmarkDotNet.Artifacts/ + +# .NET Core +project.lock.json +project.fragment.lock.json +artifacts/ +**/Properties/launchSettings.json + +# StyleCop +StyleCopReport.xml + +# Files built by Visual Studio +*_i.c +*_p.c +*_i.h +*.ilk +*.meta +*.obj +*.pch +*.pdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# Visual Studio Trace Files +*.e2e + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# JustCode is a .NET coding add-in +.JustCode + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# AxoCover is a Code Coverage Tool +.axoCover/* +!.axoCover/settings.json + +# Visual Studio code coverage results +*.coverage +*.coveragexml + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# Note: Comment the next line if you want to checkin your web deploy settings, +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# The packages folder can be ignored because of Package Restore +**/[Pp]ackages/* +# except build/, which is used as an MSBuild target. +!**/[Pp]ackages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/[Pp]ackages/repositories.config +# NuGet v3's project.json files produces more ignorable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt +*.appx + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +orleans.codegen.cs + +# Including strong name files can present a security risk +# (https://github.com/github/gitignore/pull/2483#issue-259490424) +#*.snk + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm +ServiceFabricBackup/ + +# SQL Server files +*.mdf +*.ldf +*.ndf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings +*.rptproj.rsuser + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat +node_modules/ + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) +*.vbw + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# JetBrains Rider +.idea/ +*.sln.iml + +# CodeRush +.cr/ + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + +# Cake - Uncomment if you are using it +# tools/** +# !tools/packages.config + +# Tabs Studio +*.tss + +# Telerik's JustMock configuration file +*.jmconfig + +# BizTalk build output +*.btp.cs +*.btm.cs +*.odx.cs +*.xsd.cs + +# OpenCover UI analysis results +OpenCover/ + +# Azure Stream Analytics local run output +ASALocalRun/ + +# MSBuild Binary and Structured Log +*.binlog + +# NVidia Nsight GPU debugger configuration file +*.nvuser diff --git a/CruUI.sln b/CruUI.sln new file mode 100644 index 00000000..60437b34 --- /dev/null +++ b/CruUI.sln @@ -0,0 +1,31 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 15 +VisualStudioVersion = 15.0.27130.2027 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "CruUI", "CruUI\CruUI.vcxproj", "{41F57B7F-DECE-4021-8EEC-1E8E54E9DFAC}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 + Release|x64 = Release|x64 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {41F57B7F-DECE-4021-8EEC-1E8E54E9DFAC}.Debug|x64.ActiveCfg = Debug|x64 + {41F57B7F-DECE-4021-8EEC-1E8E54E9DFAC}.Debug|x64.Build.0 = Debug|x64 + {41F57B7F-DECE-4021-8EEC-1E8E54E9DFAC}.Debug|x86.ActiveCfg = Debug|Win32 + {41F57B7F-DECE-4021-8EEC-1E8E54E9DFAC}.Debug|x86.Build.0 = Debug|Win32 + {41F57B7F-DECE-4021-8EEC-1E8E54E9DFAC}.Release|x64.ActiveCfg = Release|x64 + {41F57B7F-DECE-4021-8EEC-1E8E54E9DFAC}.Release|x64.Build.0 = Release|x64 + {41F57B7F-DECE-4021-8EEC-1E8E54E9DFAC}.Release|x86.ActiveCfg = Release|Win32 + {41F57B7F-DECE-4021-8EEC-1E8E54E9DFAC}.Release|x86.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {B0298D8B-D319-488F-99DA-9D5E6851B9F2} + EndGlobalSection +EndGlobal diff --git a/CruUI/CruUI.vcxproj b/CruUI/CruUI.vcxproj new file mode 100644 index 00000000..441d404f --- /dev/null +++ b/CruUI/CruUI.vcxproj @@ -0,0 +1,183 @@ +<?xml version="1.0" encoding="utf-8"?> +<Project DefaultTargets="Build" ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + <ItemGroup Label="ProjectConfigurations"> + <ProjectConfiguration Include="Debug|Win32"> + <Configuration>Debug</Configuration> + <Platform>Win32</Platform> + </ProjectConfiguration> + <ProjectConfiguration Include="Release|Win32"> + <Configuration>Release</Configuration> + <Platform>Win32</Platform> + </ProjectConfiguration> + <ProjectConfiguration Include="Debug|x64"> + <Configuration>Debug</Configuration> + <Platform>x64</Platform> + </ProjectConfiguration> + <ProjectConfiguration Include="Release|x64"> + <Configuration>Release</Configuration> + <Platform>x64</Platform> + </ProjectConfiguration> + </ItemGroup> + <PropertyGroup Label="Globals"> + <VCProjectVersion>15.0</VCProjectVersion> + <ProjectGuid>{41F57B7F-DECE-4021-8EEC-1E8E54E9DFAC}</ProjectGuid> + <Keyword>Win32Proj</Keyword> + <RootNamespace>CruUI</RootNamespace> + <WindowsTargetPlatformVersion>10.0.17134.0</WindowsTargetPlatformVersion> + </PropertyGroup> + <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" /> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration"> + <ConfigurationType>Application</ConfigurationType> + <UseDebugLibraries>true</UseDebugLibraries> + <PlatformToolset>v141</PlatformToolset> + <CharacterSet>Unicode</CharacterSet> + </PropertyGroup> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration"> + <ConfigurationType>Application</ConfigurationType> + <UseDebugLibraries>false</UseDebugLibraries> + <PlatformToolset>v141</PlatformToolset> + <WholeProgramOptimization>true</WholeProgramOptimization> + <CharacterSet>Unicode</CharacterSet> + </PropertyGroup> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration"> + <ConfigurationType>Application</ConfigurationType> + <UseDebugLibraries>true</UseDebugLibraries> + <PlatformToolset>v141</PlatformToolset> + <CharacterSet>Unicode</CharacterSet> + </PropertyGroup> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration"> + <ConfigurationType>Application</ConfigurationType> + <UseDebugLibraries>false</UseDebugLibraries> + <PlatformToolset>v141</PlatformToolset> + <WholeProgramOptimization>true</WholeProgramOptimization> + <CharacterSet>Unicode</CharacterSet> + </PropertyGroup> + <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" /> + <ImportGroup Label="ExtensionSettings"> + </ImportGroup> + <ImportGroup Label="Shared"> + </ImportGroup> + <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'"> + <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" /> + </ImportGroup> + <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'"> + <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" /> + </ImportGroup> + <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'"> + <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" /> + </ImportGroup> + <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|x64'"> + <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" /> + </ImportGroup> + <PropertyGroup Label="UserMacros" /> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'"> + <LinkIncremental>true</LinkIncremental> + </PropertyGroup> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'"> + <LinkIncremental>true</LinkIncremental> + <IncludePath>$(ProjectDir);$(IncludePath)</IncludePath> + </PropertyGroup> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'"> + <LinkIncremental>false</LinkIncremental> + </PropertyGroup> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'"> + <LinkIncremental>false</LinkIncremental> + </PropertyGroup> + <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'"> + <ClCompile> + <PrecompiledHeader>NotUsing</PrecompiledHeader> + <WarningLevel>Level3</WarningLevel> + <Optimization>Disabled</Optimization> + <SDLCheck>true</SDLCheck> + <PreprocessorDefinitions>WIN32;_DEBUG;_WINDOWS;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <ConformanceMode>true</ConformanceMode> + <AdditionalIncludeDirectories>$(ProjectDir);%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> + <PrecompiledHeaderFile /> + </ClCompile> + <Link> + <SubSystem>Windows</SubSystem> + <GenerateDebugInformation>true</GenerateDebugInformation> + </Link> + </ItemDefinitionGroup> + <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'"> + <ClCompile> + <PrecompiledHeader>NotUsing</PrecompiledHeader> + <WarningLevel>Level3</WarningLevel> + <Optimization>Disabled</Optimization> + <SDLCheck>true</SDLCheck> + <PreprocessorDefinitions>_SILENCE_ALL_CXX17_DEPRECATION_WARNINGS;_DEBUG;_WINDOWS;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <ConformanceMode>true</ConformanceMode> + <LanguageStandard>stdcpplatest</LanguageStandard> + </ClCompile> + <Link> + <SubSystem>Windows</SubSystem> + <GenerateDebugInformation>true</GenerateDebugInformation> + </Link> + </ItemDefinitionGroup> + <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'"> + <ClCompile> + <PrecompiledHeader>Use</PrecompiledHeader> + <WarningLevel>Level3</WarningLevel> + <Optimization>MaxSpeed</Optimization> + <FunctionLevelLinking>true</FunctionLevelLinking> + <IntrinsicFunctions>true</IntrinsicFunctions> + <SDLCheck>true</SDLCheck> + <PreprocessorDefinitions>WIN32;NDEBUG;_WINDOWS;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <ConformanceMode>true</ConformanceMode> + </ClCompile> + <Link> + <SubSystem>Windows</SubSystem> + <EnableCOMDATFolding>true</EnableCOMDATFolding> + <OptimizeReferences>true</OptimizeReferences> + <GenerateDebugInformation>true</GenerateDebugInformation> + </Link> + </ItemDefinitionGroup> + <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'"> + <ClCompile> + <PrecompiledHeader>Use</PrecompiledHeader> + <WarningLevel>Level3</WarningLevel> + <Optimization>MaxSpeed</Optimization> + <FunctionLevelLinking>true</FunctionLevelLinking> + <IntrinsicFunctions>true</IntrinsicFunctions> + <SDLCheck>true</SDLCheck> + <PreprocessorDefinitions>NDEBUG;_WINDOWS;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <ConformanceMode>true</ConformanceMode> + </ClCompile> + <Link> + <SubSystem>Windows</SubSystem> + <EnableCOMDATFolding>true</EnableCOMDATFolding> + <OptimizeReferences>true</OptimizeReferences> + <GenerateDebugInformation>true</GenerateDebugInformation> + </Link> + </ItemDefinitionGroup> + <ItemGroup> + <ClInclude Include="application.h" /> + <ClInclude Include="base.h" /> + <ClInclude Include="cru_event.h" /> + <ClInclude Include="exception.h" /> + <ClInclude Include="graph\graph.h" /> + <ClInclude Include="system_headers.h" /> + <ClInclude Include="timer.h" /> + <ClInclude Include="ui\control.h" /> + <ClInclude Include="global_macros.h" /> + <ClInclude Include="ui\events\ui_event.h" /> + <ClInclude Include="ui\layout_base.h" /> + <ClInclude Include="ui\window.h" /> + <ClInclude Include="ui\ui_base.h" /> + </ItemGroup> + <ItemGroup> + <ClCompile Include="application.cpp" /> + <ClCompile Include="cru_event.cpp" /> + <ClCompile Include="exception.cpp" /> + <ClCompile Include="main.cpp" /> + <ClCompile Include="graph\graph.cpp" /> + <ClCompile Include="timer.cpp" /> + <ClCompile Include="ui\control.cpp" /> + <ClCompile Include="ui\events\ui_event.cpp" /> + <ClCompile Include="ui\window.cpp" /> + <ClCompile Include="ui\ui_base.cpp" /> + </ItemGroup> + <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" /> + <ImportGroup Label="ExtensionTargets"> + </ImportGroup> +</Project>
\ No newline at end of file diff --git a/CruUI/CruUI.vcxproj.filters b/CruUI/CruUI.vcxproj.filters new file mode 100644 index 00000000..1967ba03 --- /dev/null +++ b/CruUI/CruUI.vcxproj.filters @@ -0,0 +1,90 @@ +<?xml version="1.0" encoding="utf-8"?> +<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + <ItemGroup> + <Filter Include="Source Files"> + <UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier> + <Extensions>cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx</Extensions> + </Filter> + <Filter Include="Header Files"> + <UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier> + <Extensions>h;hh;hpp;hxx;hm;inl;inc;xsd</Extensions> + </Filter> + <Filter Include="Resource Files"> + <UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier> + <Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions> + </Filter> + </ItemGroup> + <ItemGroup> + <ClInclude Include="application.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="base.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="ui\control.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="ui\window.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="graph\graph.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="exception.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="timer.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="cru_event.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="system_headers.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="ui\ui_base.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="ui\layout_base.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="ui\events\ui_event.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="global_macros.h"> + <Filter>Header Files</Filter> + </ClInclude> + </ItemGroup> + <ItemGroup> + <ClCompile Include="application.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="ui\control.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="ui\window.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="graph\graph.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="main.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="exception.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="timer.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="cru_event.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="ui\ui_base.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="ui\events\ui_event.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + </ItemGroup> +</Project>
\ No newline at end of file diff --git a/CruUI/application.cpp b/CruUI/application.cpp new file mode 100644 index 00000000..4fd2cf9f --- /dev/null +++ b/CruUI/application.cpp @@ -0,0 +1,96 @@ +#include "application.h" + +#include "timer.h" +#include "ui/window.h" +#include "graph/graph.h" + +namespace cru { + constexpr int invoke_later_message_id = WM_USER + 2000; + + Application* Application::instance_ = nullptr; + + Application * Application::GetInstance() { + return instance_; + } + + Application::Application(HINSTANCE h_instance) + : h_instance_(h_instance) { + + if (instance_) + throw std::runtime_error("A application instance already exists."); + + instance_ = this; + + window_manager_ = std::make_unique<ui::WindowManager>(); + graph_manager_ = std::make_unique<graph::GraphManager>(); + timer_manager_ = std::make_unique<TimerManager>(); + } + + Application::~Application() + { + instance_ = nullptr; + } + + int Application::Run() + { + MSG msg; + + while (GetMessage(&msg, nullptr, 0, 0)) + { + if (!HandleThreadMessage(msg)) + { + TranslateMessage(&msg); + DispatchMessage(&msg); + + } + } + + return static_cast<int>(msg.wParam); + } + + void Application::Quit(const int quit_code) { + ::PostQuitMessage(quit_code); + } + + bool Application::HandleThreadMessage(const MSG& message) + { + if (message.hwnd != nullptr) + return false; + + switch (message.message) + { + case invoke_later_message_id: + { + const auto p_action = reinterpret_cast<InvokeLaterAction*>(message.wParam); + (*p_action)(); + delete p_action; + return true; + } + case WM_TIMER: + { + const auto action = timer_manager_->GetAction(static_cast<UINT_PTR>(message.wParam)); + if (action.has_value()) + { + action.value()(); + return true; + } + break; + } + default: + return false; + } + return false; + } + + void InvokeLater(const InvokeLaterAction& action) { + //copy the action to a safe place + auto p_action_copy = new InvokeLaterAction(action); + + PostMessage( + nullptr, + invoke_later_message_id, + reinterpret_cast<WPARAM>(p_action_copy), + 0 + ); + } +} diff --git a/CruUI/application.h b/CruUI/application.h new file mode 100644 index 00000000..ee108cbc --- /dev/null +++ b/CruUI/application.h @@ -0,0 +1,75 @@ +#pragma once + +#include "system_headers.h" +#include <functional> +#include <memory> + +#include "base.h" + +namespace cru +{ + namespace ui + { + class WindowManager; + } + + namespace graph + { + class GraphManager; + } + + class TimerManager; + + class Application : public Object + { + public: + static Application* GetInstance(); + private: + static Application* instance_; + + public: + explicit Application(HINSTANCE h_instance); + Application(const Application&) = delete; + Application(Application&&) = delete; + Application& operator = (const Application&) = delete; + Application& operator = (Application&&) = delete; + ~Application() override; + + public: + int Run(); + void Quit(int quit_code); + + ui::WindowManager* GetWindowManager() const + { + return window_manager_.get(); + } + + graph::GraphManager* GetGraphManager() const + { + return graph_manager_.get(); + } + + TimerManager* GetTimerManager() const + { + return timer_manager_.get(); + } + + HINSTANCE GetInstanceHandle() const + { + return h_instance_; + } + + private: + bool HandleThreadMessage(const MSG& message); + + private: + HINSTANCE h_instance_; + std::unique_ptr<ui::WindowManager> window_manager_; + std::unique_ptr<graph::GraphManager> graph_manager_; + std::unique_ptr<TimerManager> timer_manager_; + }; + + + using InvokeLaterAction = std::function<void()>; + void InvokeLater(const InvokeLaterAction& action); +} diff --git a/CruUI/base.h b/CruUI/base.h new file mode 100644 index 00000000..0d401a3a --- /dev/null +++ b/CruUI/base.h @@ -0,0 +1,42 @@ +#pragma once + +#include "global_macros.h" + +#include <folly/String.h> +#include <folly/Function.h> + +namespace cru +{ + enum class FlowControl + { + Continue, + Break + }; + + using String = folly::basic_fbstring<wchar_t>; + + template<typename FunctionType> + using Function = folly::Function<FunctionType>; + + template<typename... Args> + using Action = Function<void(Args...)>; + + template<typename... Args> + using FlowControlAction = Function<FlowControl(Args...)>; + + class Object + { + public: + Object() = default; + Object(const Object&) = default; + Object& operator = (const Object&) = default; + Object(Object&&) = default; + Object& operator = (Object&&) = default; + virtual ~Object() = default; + }; + + struct Interface + { + virtual ~Interface() = default; + }; +} diff --git a/CruUI/cru_event.cpp b/CruUI/cru_event.cpp new file mode 100644 index 00000000..3977b3b2 --- /dev/null +++ b/CruUI/cru_event.cpp @@ -0,0 +1,5 @@ +#include "cru_event.h" + +namespace cru { + +} diff --git a/CruUI/cru_event.h b/CruUI/cru_event.h new file mode 100644 index 00000000..f5e548c0 --- /dev/null +++ b/CruUI/cru_event.h @@ -0,0 +1,86 @@ +#pragma once + +#include <type_traits> +#include <list> +#include <memory> +#include <algorithm> + +#include "base.h" + +namespace cru { + //Base class of all event args. + class BasicEventArgs : public Object + { + public: + explicit BasicEventArgs(Object* sender) + : sender_(sender) + { + + } + BasicEventArgs(const BasicEventArgs& other) = default; + BasicEventArgs(BasicEventArgs&& other) = default; + BasicEventArgs& operator=(const BasicEventArgs& other) = default; + BasicEventArgs& operator=(BasicEventArgs&& other) = default; + ~BasicEventArgs() override = default; + + //Get the sender of the event. + Object* GetSender() const + { + return sender_; + } + + private: + Object* sender_; + }; + + + //A non-copyable non-movable Event class. + //It stores a list of event handlers. + //TArgsType must be subclass of BasicEventArgs. + template<typename TArgsType> + class Event + { + public: + static_assert(std::is_base_of_v<BasicEventArgs, TArgsType>, + "TArgsType must be subclass of BasicEventArgs."); + + + using ArgsType = TArgsType; + using EventHandler = Action<ArgsType&>; + using EventHandlerPtr = std::shared_ptr<EventHandler>; + + Event() = default; + Event(const Event&) = delete; + Event& operator = (const Event&) = delete; + Event(Event&&) = delete; + Event& operator = (Event&&) = delete; + ~Event() = default; + + //Create a EventHandlerPtr from the given handler, + //add it to list and return it. + EventHandlerPtr AddHandler(EventHandler&& handler) + { + EventHandlerPtr ptr = std::make_shared<EventHandler>(std::move(handler)); + handlers_.push_back(ptr); + return ptr; + } + + void AddHandler(EventHandlerPtr handler) { + handlers_.push_back(handler); + } + + void RemoveHandler(EventHandlerPtr handler) { + auto find_result = std::find(handlers_.cbegin(), handlers_.cend(), handler); + if (find_result != handlers_.cend()) + handlers_.erase(find_result); + } + + void Raise(ArgsType& args) { + for (auto ptr : handlers_) + (*ptr)(args); + } + + private: + std::list<EventHandlerPtr> handlers_; + }; +} diff --git a/CruUI/exception.cpp b/CruUI/exception.cpp new file mode 100644 index 00000000..45af254d --- /dev/null +++ b/CruUI/exception.cpp @@ -0,0 +1,29 @@ +#include "exception.h" + +#include <sstream> +#include <iomanip> + +namespace cru +{ + HResultError::HResultError(const HRESULT h_result) + : runtime_error(MakeMessage(h_result, std::nullopt)), h_result_(h_result) + { + + } + + HResultError::HResultError(const HRESULT h_result, const std::string& message) + : runtime_error(MakeMessage(h_result, std::make_optional(message))), h_result_(h_result) + { + + } + + std::string HResultError::MakeMessage(HRESULT h_result, std::optional<std::string> message) + { + std::stringstream ss; + ss << "An HResultError is thrown. HRESULT: 0x" << std::setfill('0') + << std::setw(sizeof h_result * 2) << std::hex << h_result << "."; + if (message.has_value()) + ss << "Additional message: " << message.value(); + return ss.str(); + } +} diff --git a/CruUI/exception.h b/CruUI/exception.h new file mode 100644 index 00000000..c7d6f996 --- /dev/null +++ b/CruUI/exception.h @@ -0,0 +1,42 @@ +#pragma once + +#include "system_headers.h" +#include <optional> + +#include "base.h" + + +namespace cru { + class HResultError : public std::runtime_error + { + public: + explicit HResultError(HRESULT h_result); + HResultError(HRESULT h_result, const std::string& message); + HResultError(const HResultError& other) = default; + HResultError(HResultError&& other) = default; + HResultError& operator=(const HResultError& other) = default; + HResultError& operator=(HResultError&& other) = default; + ~HResultError() override = default; + + HRESULT GetHResult() const + { + return h_result_; + } + + private: + static std::string MakeMessage(HRESULT h_result, std::optional<std::string> message); + + private: + HRESULT h_result_; + }; + + inline void ThrowIfFailed(const HRESULT h_result) { + if (FAILED(h_result)) + throw HResultError(h_result); + } + + inline void ThrowIfFailed(const HRESULT h_result, const std::string& message) { + if (FAILED(h_result)) + throw HResultError(h_result, message); + } +} diff --git a/CruUI/global_macros.h b/CruUI/global_macros.h new file mode 100644 index 00000000..fcb93174 --- /dev/null +++ b/CruUI/global_macros.h @@ -0,0 +1,3 @@ +#pragma once + +#define GLOG_NO_ABBREVIATED_SEVERITIES diff --git a/CruUI/graph/graph.cpp b/CruUI/graph/graph.cpp new file mode 100644 index 00000000..4871c5f4 --- /dev/null +++ b/CruUI/graph/graph.cpp @@ -0,0 +1,233 @@ +#include "graph.h" + +#include "application.h" +#include "exception.h" + +namespace cru { + namespace graph { + using Microsoft::WRL::ComPtr; + + WindowRenderTarget::WindowRenderTarget(GraphManager* graph_manager, HWND hwnd) + { + this->graph_manager_ = graph_manager; + + const auto d3d11_device = graph_manager->GetD3D11Device(); + const auto dxgi_factory = graph_manager->GetDxgiFactory(); + + // Allocate a descriptor. + DXGI_SWAP_CHAIN_DESC1 swap_chain_desc = { 0 }; + swap_chain_desc.Width = 0; // use automatic sizing + swap_chain_desc.Height = 0; + swap_chain_desc.Format = DXGI_FORMAT_B8G8R8A8_UNORM; // this is the most common swapchain format + swap_chain_desc.Stereo = false; + swap_chain_desc.SampleDesc.Count = 1; // don't use multi-sampling + swap_chain_desc.SampleDesc.Quality = 0; + swap_chain_desc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT; + swap_chain_desc.BufferCount = 2; // use double buffering to enable flip + swap_chain_desc.Scaling = DXGI_SCALING_NONE; + swap_chain_desc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL; // all apps must use this SwapEffect + swap_chain_desc.Flags = 0; + + + + // Get the final swap chain for this window from the DXGI factory. + ThrowIfFailed( + dxgi_factory->CreateSwapChainForHwnd( + d3d11_device.Get(), + hwnd, + &swap_chain_desc, + nullptr, + nullptr, + &dxgi_swap_chain_ + ) + ); + + CreateTargetBitmap(); + } + + WindowRenderTarget::~WindowRenderTarget() + { + + } + + void WindowRenderTarget::ResizeBuffer(const int width, const int height) + { + const auto graph_manager = graph_manager_; + const auto d2d1_device_context = graph_manager->GetD2D1DeviceContext(); + + ComPtr<ID2D1Image> old_target; + d2d1_device_context->GetTarget(&old_target); + const auto target_this = old_target == this->target_bitmap_; + if (target_this) + d2d1_device_context->SetTarget(nullptr); + + old_target = nullptr; + target_bitmap_ = nullptr; + + ThrowIfFailed( + dxgi_swap_chain_->ResizeBuffers(0, width, height, DXGI_FORMAT_UNKNOWN, 0) + ); + + CreateTargetBitmap(); + + if (target_this) + d2d1_device_context->SetTarget(target_bitmap_.Get()); + } + + void WindowRenderTarget::SetAsTarget() + { + GetD2DDeviceContext()->SetTarget(target_bitmap_.Get()); + } + + void WindowRenderTarget::Present() + { + ThrowIfFailed( + dxgi_swap_chain_->Present(1, 0) + ); + } + + void WindowRenderTarget::CreateTargetBitmap() + { + // Direct2D needs the dxgi version of the backbuffer surface pointer. + ComPtr<IDXGISurface> dxgiBackBuffer; + ThrowIfFailed( + dxgi_swap_chain_->GetBuffer(0, IID_PPV_ARGS(&dxgiBackBuffer)) + ); + + const auto dpi = graph_manager_->GetDpi(); + + auto bitmap_properties = + D2D1::BitmapProperties1( + D2D1_BITMAP_OPTIONS_TARGET | D2D1_BITMAP_OPTIONS_CANNOT_DRAW, + D2D1::PixelFormat(DXGI_FORMAT_B8G8R8A8_UNORM, D2D1_ALPHA_MODE_IGNORE), + dpi.x, + dpi.y + ); + + // Get a D2D surface from the DXGI back buffer to use as the D2D render target. + ThrowIfFailed( + graph_manager_->GetD2D1DeviceContext()->CreateBitmapFromDxgiSurface( + dxgiBackBuffer.Get(), + &bitmap_properties, + &target_bitmap_ + ) + ); + } + + GraphManager::GraphManager() + { + UINT creation_flags = D3D11_CREATE_DEVICE_BGRA_SUPPORT; + +#ifdef _DEBUG + creation_flags |= D3D11_CREATE_DEVICE_DEBUG; +#endif + + const D3D_FEATURE_LEVEL feature_levels[] = + { + D3D_FEATURE_LEVEL_11_1, + D3D_FEATURE_LEVEL_11_0, + D3D_FEATURE_LEVEL_10_1, + D3D_FEATURE_LEVEL_10_0, + D3D_FEATURE_LEVEL_9_3, + D3D_FEATURE_LEVEL_9_2, + D3D_FEATURE_LEVEL_9_1 + }; + + + ThrowIfFailed(D3D11CreateDevice( + nullptr, + D3D_DRIVER_TYPE_HARDWARE, + nullptr, + creation_flags, + feature_levels, + ARRAYSIZE(feature_levels), + D3D11_SDK_VERSION, + &d3d11_device_, + nullptr, + &d3d11_device_context_ + )); + + Microsoft::WRL::ComPtr<IDXGIDevice> dxgi_device; + + ThrowIfFailed(d3d11_device_.As(&dxgi_device)); + + ThrowIfFailed(D2D1CreateFactory( + D2D1_FACTORY_TYPE_SINGLE_THREADED, + __uuidof(ID2D1Factory1), + &d2d1_factory_ + )); + + ThrowIfFailed(d2d1_factory_->CreateDevice(dxgi_device.Get(), &d2d1_device_)); + + ThrowIfFailed(d2d1_device_->CreateDeviceContext( + D2D1_DEVICE_CONTEXT_OPTIONS_NONE, + &d2d1_device_context_ + )); + + // Identify the physical adapter (GPU or card) this device is runs on. + ComPtr<IDXGIAdapter> dxgi_adapter; + ThrowIfFailed( + dxgi_device->GetAdapter(&dxgi_adapter) + ); + + // Get the factory object that created the DXGI device. + ThrowIfFailed( + dxgi_adapter->GetParent(IID_PPV_ARGS(&dxgi_factory_)) + ); + } + + GraphManager::~GraphManager() + { + + } + + std::shared_ptr<WindowRenderTarget> GraphManager::CreateWindowRenderTarget(HWND hwnd) + { + return std::make_shared<WindowRenderTarget>(this, hwnd); + } + + Dpi GraphManager::GetDpi() + { + Dpi dpi; + d2d1_factory_->GetDesktopDpi(&dpi.x, &dpi.y); + return dpi; + } + + void GraphManager::ReloadSystemMetrics() + { + ThrowIfFailed( + d2d1_factory_->ReloadSystemMetrics() + ); + } + + inline int DipToPixelInternal(float dip, float dpi) + { + return static_cast<int>(dip * dpi / 96.0f); + } + + int DipToPixelX(float dipX) + { + return DipToPixelInternal(dipX, Application::GetInstance()->GetGraphManager()->GetDpi().x); + } + + int DipToPixelY(float dipY) + { + return DipToPixelInternal(dipY, Application::GetInstance()->GetGraphManager()->GetDpi().y); + } + + inline float DipToPixelInternal(int pixel, float dpi) + { + return static_cast<float>(pixel) * 96.0f / dpi; + } + + float PixelToDipX(int pixelX) + { + return DipToPixelInternal(pixelX, Application::GetInstance()->GetGraphManager()->GetDpi().x); + } + + float PixelToDipY(int pixelY) + { + return DipToPixelInternal(pixelY, Application::GetInstance()->GetGraphManager()->GetDpi().y); + } + } +} diff --git a/CruUI/graph/graph.h b/CruUI/graph/graph.h new file mode 100644 index 00000000..69d11b9c --- /dev/null +++ b/CruUI/graph/graph.h @@ -0,0 +1,126 @@ +#pragma once + +#include "system_headers.h" +#include <memory> + +#include "base.h" + + +namespace cru +{ + namespace graph + { + class GraphManager; + + //Represents a window render target. + class WindowRenderTarget : public Object + { + public: + WindowRenderTarget(GraphManager* graph_manager, HWND hwnd); + WindowRenderTarget(const WindowRenderTarget& other) = delete; + WindowRenderTarget(WindowRenderTarget&& other) = delete; + WindowRenderTarget& operator=(const WindowRenderTarget& other) = delete; + WindowRenderTarget& operator=(WindowRenderTarget&& other) = delete; + ~WindowRenderTarget() override; + + public: + //Get the graph manager that created the render target. + GraphManager* GetGraphManager() const + { + return graph_manager_; + } + + //Get the d2d device context. + inline Microsoft::WRL::ComPtr<ID2D1DeviceContext> GetD2DDeviceContext() const; + + //Get the target bitmap which can be set as the ID2D1DeviceContext's target. + Microsoft::WRL::ComPtr<ID2D1Bitmap1> GetTargetBitmap() const + { + return target_bitmap_; + } + + //Resize the underlying buffer. + void ResizeBuffer(int width, int height); + + //Set this render target as the d2d device context's target. + void SetAsTarget(); + + //Present the data of the underlying buffer to the window. + void Present(); + + private: + void CreateTargetBitmap(); + + private: + GraphManager* graph_manager_; + Microsoft::WRL::ComPtr<IDXGISwapChain1> dxgi_swap_chain_; + Microsoft::WRL::ComPtr<ID2D1Bitmap1> target_bitmap_; + }; + + struct Dpi + { + float x; + float y; + }; + + class GraphManager : public Object + { + public: + GraphManager(); + GraphManager(const GraphManager& other) = delete; + GraphManager(GraphManager&& other) = delete; + GraphManager& operator=(const GraphManager& other) = delete; + GraphManager& operator=(GraphManager&& other) = delete; + ~GraphManager() override; + + public: + Microsoft::WRL::ComPtr<ID2D1Factory1> GetD2D1Factory() const + { + return d2d1_factory_; + } + + Microsoft::WRL::ComPtr<ID2D1DeviceContext> GetD2D1DeviceContext() const + { + return d2d1_device_context_; + } + + Microsoft::WRL::ComPtr<ID3D11Device> GetD3D11Device() const + { + return d3d11_device_; + } + + Microsoft::WRL::ComPtr<IDXGIFactory2> GetDxgiFactory() const + { + return dxgi_factory_; + } + + //Create a window render target with the HWND. + std::shared_ptr<WindowRenderTarget> CreateWindowRenderTarget(HWND hwnd); + + //Get the desktop dpi. + Dpi GetDpi(); + + //Reload system metrics including desktop dpi. + void ReloadSystemMetrics(); + + private: + Microsoft::WRL::ComPtr<ID3D11Device> d3d11_device_; + Microsoft::WRL::ComPtr<ID3D11DeviceContext> d3d11_device_context_; + Microsoft::WRL::ComPtr<ID2D1Factory1> d2d1_factory_; + Microsoft::WRL::ComPtr<ID2D1Device> d2d1_device_; + Microsoft::WRL::ComPtr<ID2D1DeviceContext> d2d1_device_context_; + Microsoft::WRL::ComPtr<IDXGIFactory2> dxgi_factory_; + Microsoft::WRL::ComPtr<IDWriteFactory> dwrite_factory_; + }; + + int DipToPixelX(float dip_x); + int DipToPixelY(float dip_y); + float PixelToDipX(int pixel_x); + float PixelToDipY(int pixel_y); + + Microsoft::WRL::ComPtr<ID2D1DeviceContext> WindowRenderTarget::GetD2DDeviceContext() const + { + return graph_manager_->GetD2D1DeviceContext(); + } + } +} diff --git a/CruUI/main.cpp b/CruUI/main.cpp Binary files differnew file mode 100644 index 00000000..185dd90d --- /dev/null +++ b/CruUI/main.cpp diff --git a/CruUI/system_headers.h b/CruUI/system_headers.h new file mode 100644 index 00000000..bd33b5f6 --- /dev/null +++ b/CruUI/system_headers.h @@ -0,0 +1,20 @@ +#pragma once + + +//include system headers + +#define NOMINMAX +#include <Windows.h> +#include <windowsx.h> + +#pragma comment(lib, "D3D11.lib") +#include <d3d11.h> + +#pragma comment(lib, "D2d1.lib") +#include <d2d1_1.h> + +#pragma comment(lib, "DWrite.lib") +#include <dwrite.h> + +#include <dxgi1_2.h> +#include <wrl/client.h> diff --git a/CruUI/timer.cpp b/CruUI/timer.cpp new file mode 100644 index 00000000..5eb803c7 --- /dev/null +++ b/CruUI/timer.cpp @@ -0,0 +1,97 @@ +#include "timer.h" + +namespace cru +{ + TimerManager* TimerManager::instance_ = nullptr; + + TimerManager* TimerManager::GetInstance() + { + return instance_; + } + + TimerManager::TimerManager() + { + instance_ = this; + } + + TimerManager::~TimerManager() + { + instance_ = nullptr; + } + + UINT_PTR TimerManager::CreateTimer(const UINT microseconds, const bool loop, const TimerAction & action) + { + auto id = ::SetTimer(nullptr, 0, microseconds, nullptr); + if (loop) + map_[id] = action; + else + map_[id] = [this, action, id]() { + action(); + this->KillTimer(id); + }; + + return id; + } + + void TimerManager::KillTimer(const UINT_PTR id) + { + const auto find_result = map_.find(id); + if (find_result != map_.cend()) + { + ::KillTimer(nullptr, id); + map_.erase(find_result); + } + } + + std::optional<TimerAction> TimerManager::GetAction(const UINT_PTR id) + { + auto find_result = map_.find(id); + if (find_result == map_.cend()) + return std::nullopt; + return find_result->second; + } + + class TimerTaskImpl : public ITimerTask + { + public: + explicit TimerTaskImpl(UINT_PTR id); + TimerTaskImpl(const TimerTaskImpl& other) = delete; + TimerTaskImpl(TimerTaskImpl&& other) = delete; + TimerTaskImpl& operator=(const TimerTaskImpl& other) = delete; + TimerTaskImpl& operator=(TimerTaskImpl&& other) = delete; + ~TimerTaskImpl() override = default; + + void Cancel() override; + + private: + UINT_PTR id_; + }; + + TimerTaskImpl::TimerTaskImpl(const UINT_PTR id) + : id_(id) + { + + } + + void TimerTaskImpl::Cancel() + { + TimerManager::GetInstance()->KillTimer(id_); + } + + inline UINT SecondToMicroSecond(const double seconds) + { + return static_cast<UINT>(seconds * 1000); + } + + std::shared_ptr<ITimerTask> SetTimeout(const double seconds, const TimerAction & action) + { + auto id = TimerManager::GetInstance()->CreateTimer(SecondToMicroSecond(seconds), false, action); + return std::make_shared<TimerTaskImpl>(id); + } + + std::shared_ptr<ITimerTask> SetInterval(const double seconds, const TimerAction & action) + { + auto id = TimerManager::GetInstance()->CreateTimer(SecondToMicroSecond(seconds), true, action); + return std::make_shared<TimerTaskImpl>(id); + } +} diff --git a/CruUI/timer.h b/CruUI/timer.h new file mode 100644 index 00000000..1a512a44 --- /dev/null +++ b/CruUI/timer.h @@ -0,0 +1,49 @@ +#pragma once + + +#include "system_headers.h" +#include <functional> +#include <memory> +#include <map> +#include <optional> + +#include "base.h" +#include "application.h" + +namespace cru +{ + using TimerAction = std::function<void()>; + + class TimerManager : public Object + { + friend class cru::Application; + private: + static TimerManager* instance_; + + public: + static TimerManager* GetInstance(); + + public: + TimerManager(); + TimerManager(const TimerManager& other) = delete; + TimerManager(TimerManager&& other) = delete; + TimerManager& operator=(const TimerManager& other) = delete; + TimerManager& operator=(TimerManager&& other) = delete; + ~TimerManager() override; + + UINT_PTR CreateTimer(UINT microseconds, bool loop, const TimerAction& action); + void KillTimer(UINT_PTR id); + std::optional<TimerAction> GetAction(UINT_PTR id); + + private: + std::map<UINT_PTR, TimerAction> map_{}; + }; + + struct ITimerTask : virtual Interface + { + virtual void Cancel() = 0; + }; + + std::shared_ptr<ITimerTask> SetTimeout(double seconds, const TimerAction& action); + std::shared_ptr<ITimerTask> SetInterval(double seconds, const TimerAction& action); +} diff --git a/CruUI/ui/control.cpp b/CruUI/ui/control.cpp new file mode 100644 index 00000000..32edc0cc --- /dev/null +++ b/CruUI/ui/control.cpp @@ -0,0 +1,488 @@ +#include "control.h" + +#include <algorithm> + +#include "window.h" + +namespace cru { + namespace ui { + using namespace events; + + Control::Control() : + window_(nullptr), + parent_(nullptr), + position_(Point::zero), + size_(Size::zero), + is_mouse_inside_(false), + layout_params_(nullptr), + desired_size_(Size::zero) + { + + } + + void Control::ForeachChild(Action<Control*>&& predicate) + { + for (const auto child : children_) + predicate(child); + } + + void Control::ForeachChild(FlowControlAction<Control*>&& predicate) + { + for (const auto child : children_) + { + if (predicate(child) == FlowControl::Break) + break; + } + } + + std::vector<Control*> Control::GetChildren() + { + return this->children_; + } + + void AddChildCheck(Control* control) + { + if (control->GetParent() != nullptr) + throw std::invalid_argument("The control already has a parent."); + + if (dynamic_cast<Window*>(control)) + throw std::invalid_argument("Can't add a window as child."); + } + + void Control::AddChild(Control* control) + { + AddChildCheck(control); + + this->children_.push_back(control); + + control->parent_ = this; + + this->OnAddChild(control); + } + + void Control::AddChild(Control* control, int position) + { + AddChildCheck(control); + + if (position < 0 || static_cast<decltype(this->children_.size())>(position) > this->children_.size()) + throw std::invalid_argument("The position is out of range."); + + this->children_.insert(this->children_.cbegin() + position, control); + + control->parent_ = this; + + this->OnAddChild(this); + } + + void Control::RemoveChild(Control* child) + { + const auto i = std::find(this->children_.cbegin(), this->children_.cend(), child); + if (i == this->children_.cend()) + throw std::invalid_argument("The argument child is not a child of this control."); + + this->children_.erase(i); + + child->parent_ = nullptr; + + this->OnRemoveChild(this); + } + + void Control::RemoveChild(int position) + { + if (position < 0 || static_cast<decltype(this->children_.size())>(position) >= this->children_.size()) + throw std::invalid_argument("The position is out of range."); + + const auto p = children_.cbegin() + position; + const auto child = *p; + children_.erase(p); + + child->parent_ = nullptr; + + this->OnRemoveChild(child); + } + + Control* Control::GetAncestor() + { + // if attached to window, the window is the ancestor. + if (window_) + return window_; + + // otherwise find the ancestor + auto ancestor = this; + while (const auto parent = ancestor->GetParent()) + ancestor = parent; + return ancestor; + } + + Window * Control::GetWindow() + { + return window_; + } + + void TraverseDescendantsInternal(Control* control, + const std::function<void(Control*)>& predicate) + { + predicate(control); + control->ForeachChild([predicate](Control* c) { + TraverseDescendantsInternal(c, predicate); + }); + } + + void Control::TraverseDescendants( + const std::function<void(Control*)>& predicate) + { + TraverseDescendantsInternal(this, predicate); + } + + Point Control::GetPositionRelative() + { + return position_; + } + + void Control::SetPositionRelative(const Point & position) + { + position_ = position; + if (auto window = GetWindow()) + { + window->GetLayoutManager()->InvalidateControlPositionCache(this); + window->Repaint(); + } + //TODO: Position change notify. + } + + Size Control::GetSize() + { + return size_; + } + + void Control::SetSize(const Size & size) + { + const auto old_size = size_; + size_ = size; + SizeChangedEventArgs args(this, this, old_size, size); + OnSizeChangedCore(args); + if (auto window = GetWindow()) + window->Repaint(); + } + + Point Control::GetPositionAbsolute() + { + return position_cache_.lefttop_position_absolute; + } + + Point Control::LocalToAbsolute(const Point& point) + { + return Point(point.x + position_cache_.lefttop_position_absolute.x, + point.y + position_cache_.lefttop_position_absolute.y); + } + + Point Control::AbsoluteToLocal(const Point & point) + { + return Point(point.x - position_cache_.lefttop_position_absolute.x, + point.y - position_cache_.lefttop_position_absolute.y); + } + + bool Control::IsPointInside(const Point & point) + { + auto size = GetSize(); + return point.x >= 0.0f && point.x < size.width && point.y >= 0.0f && point.y < size.height; + } + + void Control::Draw(ID2D1DeviceContext* device_context) + { + D2D1::Matrix3x2F old_transform; + device_context->GetTransform(&old_transform); + + auto position = GetPositionRelative(); + device_context->SetTransform(old_transform * D2D1::Matrix3x2F::Translation(position.x, position.y)); + + OnDraw(device_context); + DrawEventArgs args(this, this, device_context); + draw_event.Raise(args); + + for (auto child : GetChildren()) + child->Draw(device_context); + + device_context->SetTransform(old_transform); + } + + bool Control::RequestFocus() + { + auto window = GetWindow(); + if (window == nullptr) + return false; + + return window->RequestFocusFor(this); + } + + bool Control::HasFocus() + { + auto window = GetWindow(); + if (window == nullptr) + return false; + + return window->GetFocusControl() == this; + } + + void Control::Measure(const Size& available_size) + { + SetDesiredSize(OnMeasure(available_size)); + } + + void Control::Layout(const Rect& rect) + { + SetPositionRelative(rect.GetLeftTop()); + SetSize(rect.GetSize()); + OnLayout(rect); + } + + Size Control::GetDesiredSize() + { + return desired_size_; + } + + void Control::SetDesiredSize(const Size& desired_size) + { + desired_size_ = desired_size; + } + + void Control::OnAddChild(Control* child) + { + if (auto window = dynamic_cast<Window*>(GetAncestor())) + { + child->TraverseDescendants([window](Control* control) { + control->OnAttachToWindow(window); + }); + window->RefreshControlList(); + + } + } + + void Control::OnRemoveChild(Control* child) + { + if (auto window = dynamic_cast<Window*>(GetAncestor())) + { + child->TraverseDescendants([window](Control* control) { + control->OnDetachToWindow(window); + }); + window->RefreshControlList(); + } + } + + void Control::OnAttachToWindow(Window* window) + { + window_ = window; + } + + void Control::OnDetachToWindow(Window * window) + { + window_ = nullptr; + } + + void Control::OnDraw(ID2D1DeviceContext * device_context) + { + + } + + void Control::OnPositionChanged(PositionChangedEventArgs & args) + { + + } + + void Control::OnSizeChanged(SizeChangedEventArgs & args) + { + } + + void Control::OnPositionChangedCore(PositionChangedEventArgs & args) + { + OnPositionChanged(args); + position_changed_event.Raise(args); + } + + void Control::OnSizeChangedCore(SizeChangedEventArgs & args) + { + OnSizeChanged(args); + size_changed_event.Raise(args); + } + + void Control::OnMouseEnter(MouseEventArgs & args) + { + } + + void Control::OnMouseLeave(MouseEventArgs & args) + { + } + + void Control::OnMouseMove(MouseEventArgs & args) + { + } + + void Control::OnMouseDown(MouseButtonEventArgs & args) + { + } + + void Control::OnMouseUp(MouseButtonEventArgs & args) + { + } + + void Control::OnMouseEnterCore(MouseEventArgs & args) + { + is_mouse_inside_ = true; + OnMouseEnter(args); + mouse_enter_event.Raise(args); + } + + void Control::OnMouseLeaveCore(MouseEventArgs & args) + { + is_mouse_inside_ = false; + OnMouseLeave(args); + mouse_leave_event.Raise(args); + } + + void Control::OnMouseMoveCore(MouseEventArgs & args) + { + OnMouseMove(args); + mouse_move_event.Raise(args); + } + + void Control::OnMouseDownCore(MouseButtonEventArgs & args) + { + OnMouseDown(args); + mouse_down_event.Raise(args); + } + + void Control::OnMouseUpCore(MouseButtonEventArgs & args) + { + OnMouseUp(args); + mouse_up_event.Raise(args); + } + + void Control::OnGetFocus(UiEventArgs & args) + { + } + + void Control::OnLoseFocus(UiEventArgs & args) + { + } + + void Control::OnGetFocusCore(UiEventArgs & args) + { + OnGetFocus(args); + get_focus_event.Raise(args); + } + + void Control::OnLoseFocusCore(UiEventArgs & args) + { + OnLoseFocus(args); + lose_focus_event.Raise(args); + } + + Size Control::OnMeasure(const Size& available_size) + { + const auto layout_params = GetLayoutParams(); + +#ifdef _DEBUG + if (!layout_params->Validate()) + ::OutputDebugStringW(L"LayoutParams is not valid."); +#endif + + auto&& f = [&]( + const MeasureLength& layout_length, + const float available_length, + const std::optional<float> max_length, + const std::optional<float> min_length + ) -> float + { + float length; + switch (layout_length.mode) + { + case MeasureMode::Exactly: + { + length = std::maxlayout_length.length; + break; + } + case MeasureMode::Stretch: + case MeasureMode::Content: + return available_length; + default: + return 0.0f; + } + if (max_length.has_value()) + length = std::min(max_length.value(), length); + + }; + + Size size_for_children; + size_for_children.width = f(layout_params->size.width, available_size.width); + size_for_children.height = f(layout_params->size.height, available_size.height); + + + //TODO! + } + + void Control::OnLayout(const Rect& rect) + { + //TODO! + } + + std::list<Control*> GetAncestorList(Control* control) + { + std::list<Control*> l; + while (control != nullptr) + { + l.push_front(control); + control = control->GetParent(); + } + return l; + } + + Control* FindLowestCommonAncestor(Control * left, Control * right) + { + if (left == nullptr || right == nullptr) + return nullptr; + + auto&& left_list = GetAncestorList(left); + auto&& right_list = GetAncestorList(right); + + // the root is different + if (left_list.front() != right_list.front()) + return nullptr; + + // find the last same control or the last control (one is the other's ancestor) + auto left_i = left_list.cbegin(); + auto right_i = right_list.cbegin(); + while (true) + { + if (left_i == left_list.cend()) + return *(--left_i); + if (right_i == right_list.cend()) + return *(--right_i); + if (*left_i != *right_i) + return *(--left_i); + ++left_i; + ++right_i; + } + } + + Control * IsAncestorOrDescendant(Control * left, Control * right) + { + //Search up along the trunk from "left". Return if find "right". + auto control = left; + while (control != nullptr) + { + if (control == right) + return control; + control = control->GetParent(); + } + //Search up along the trunk from "right". Return if find "left". + control = right; + while (control != nullptr) + { + if (control == left) + return control; + control = control->GetParent(); + } + return nullptr; + } + } +} diff --git a/CruUI/ui/control.h b/CruUI/ui/control.h new file mode 100644 index 00000000..e7fe2d45 --- /dev/null +++ b/CruUI/ui/control.h @@ -0,0 +1,248 @@ +#pragma once + +#include "system_headers.h" +#include <vector> +#include <optional> + +#include "base.h" +#include "ui_base.h" +#include "layout_base.h" +#include "events/ui_event.h" + +namespace cru +{ + namespace ui + { + class Control; + class Window; + + + //the position cache + struct ControlPositionCache + { + //The lefttop relative to the ancestor. + Point lefttop_position_absolute; + }; + + + class Control : public Object + { + friend class Window; + friend class WindowLayoutManager; + protected: + Control(); + + public: + Control(const Control& other) = delete; + Control(Control&& other) = delete; + Control& operator=(const Control& other) = delete; + Control& operator=(Control&& other) = delete; + ~Control() override = default; + + public: + + //*************** region: tree *************** + + //Get parent of control, return nullptr if it has no parent. + Control* GetParent() const + { + return parent_; + } + + //Traverse the children + void ForeachChild(Action<Control*>&& predicate); + void ForeachChild(FlowControlAction<Control*>&& predicate); + + //Return a vector of all children. This function will create a + //temporary copy of vector of children. If you just want to + //traverse all children, just call ForeachChild. + std::vector<Control*> GetChildren(); + + //Add a child at tail. + void AddChild(Control* control); + + //Add a child before the position. + void AddChild(Control* control, int position); + + //Remove a child. + void RemoveChild(Control* child); + + //Remove a child at specified position. + void RemoveChild(int position); + + //Get the ancestor of the control. + Control* GetAncestor(); + + //Get the window if attached, otherwise, return nullptr. + Window* GetWindow(); + + //Traverse the tree rooted the control. + void TraverseDescendants(const std::function<void(Control*)>& predicate); + + //*************** region: position and size *************** + // Position and size part must be isolated from layout part. + // All the operations in this part must be done independently. + // And layout part must use api of this part. + + //Get the lefttop relative to its parent. + virtual Point GetPositionRelative(); + + //Set the lefttop relative to its parent. + virtual void SetPositionRelative(const Point& position); + + //Get the actual size. + virtual Size GetSize(); + + //Set the actual size directly without relayout. + virtual void SetSize(const Size& size); + + //Get lefttop relative to ancestor. This is only valid when + //attached to window. Notice that the value is cached. + //You can invalidate and recalculate it by calling "InvalidatePositionCache". + Point GetPositionAbsolute(); + + //Local point to absolute point. + Point LocalToAbsolute(const Point& point); + + //Absolute point to local point. + Point AbsoluteToLocal(const Point& point); + + bool IsPointInside(const Point& point); + + + //*************** region: graphic *************** + + //Draw this control and its child controls. + void Draw(ID2D1DeviceContext* device_context); + + //*************** region: focus *************** + + bool RequestFocus(); + + bool HasFocus(); + + + //*************** region: layout *************** + + void Measure(const Size& available_size); + + void Layout(const Rect& rect); + + Size GetDesiredSize(); + + void SetDesiredSize(const Size& desired_size); + + template<typename TLayoutParams = BasicLayoutParams> + std::shared_ptr<TLayoutParams> GetLayoutParams() + { + static_assert(std::is_base_of_v<BasicLayoutParams, TLayoutParams>, "TLayoutParams must be subclass of BasicLayoutParams."); + return static_cast<std::shared_ptr<BasicLayoutParams>>(layout_params_); + } + + template<typename TLayoutParams = BasicLayoutParams, + typename = std::enable_if_t<std::is_base_of_v<BasicLayoutParams, TLayoutParams>>> + void SetLayoutParams(std::shared_ptr<TLayoutParams> basic_layout_params) + { + static_assert(std::is_base_of_v<BasicLayoutParams, TLayoutParams>, "TLayoutParams must be subclass of BasicLayoutParams."); + layout_params_ = basic_layout_params; + } + + //*************** region: events *************** + //Raised when mouse enter the control. + events::MouseEvent mouse_enter_event; + //Raised when mouse is leave the control. + events::MouseEvent mouse_leave_event; + //Raised when mouse is move in the control. + events::MouseEvent mouse_move_event; + //Raised when a mouse button is pressed in the control. + events::MouseButtonEvent mouse_down_event; + //Raised when a mouse button is released in the control. + events::MouseButtonEvent mouse_up_event; + + events::UiEvent get_focus_event; + events::UiEvent lose_focus_event; + + events::DrawEvent draw_event; + + events::PositionChangedEvent position_changed_event; + events::SizeChangedEvent size_changed_event; + + protected: + //Invoked when a child is added. Overrides should invoke base. + virtual void OnAddChild(Control* child); + //Invoked when a child is removed. Overrides should invoke base. + virtual void OnRemoveChild(Control* child); + + //Invoked when the control is attached to a window. Overrides should invoke base. + virtual void OnAttachToWindow(Window* window); + //Invoked when the control is detached to a window. Overrides should invoke base. + virtual void OnDetachToWindow(Window* window); + + virtual void OnDraw(ID2D1DeviceContext* device_context); + + + // For a event, the window event system will first dispatch event to core functions. + // Therefore for particular controls, you should do essential actions in core functions, + // and override version should invoke base version. The base core function + // in "Control" class will call corresponding non-core function and call "Raise" on + // event objects. So user custom actions should be done by overriding non-core function + // and calling the base version is optional. + + //*************** region: position and size event *************** + virtual void OnPositionChanged(events::PositionChangedEventArgs& args); + virtual void OnSizeChanged(events::SizeChangedEventArgs& args); + + virtual void OnPositionChangedCore(events::PositionChangedEventArgs& args); + virtual void OnSizeChangedCore(events::SizeChangedEventArgs& args); + + + //*************** region: mouse event *************** + virtual void OnMouseEnter(events::MouseEventArgs& args); + virtual void OnMouseLeave(events::MouseEventArgs& args); + virtual void OnMouseMove(events::MouseEventArgs& args); + virtual void OnMouseDown(events::MouseButtonEventArgs& args); + virtual void OnMouseUp(events::MouseButtonEventArgs& args); + + virtual void OnMouseEnterCore(events::MouseEventArgs& args); + virtual void OnMouseLeaveCore(events::MouseEventArgs& args); + virtual void OnMouseMoveCore(events::MouseEventArgs& args); + virtual void OnMouseDownCore(events::MouseButtonEventArgs& args); + virtual void OnMouseUpCore(events::MouseButtonEventArgs& args); + + + //*************** region: focus event *************** + virtual void OnGetFocus(events::UiEventArgs& args); + virtual void OnLoseFocus(events::UiEventArgs& args); + + virtual void OnGetFocusCore(events::UiEventArgs& args); + virtual void OnLoseFocusCore(events::UiEventArgs& args); + + //*************** region: layout *************** + virtual Size OnMeasure(const Size& available_size); + virtual void OnLayout(const Rect& rect); + + private: + Window * window_; + + Control * parent_; + std::vector<Control*> children_; + + Point position_; + Size size_; + + ControlPositionCache position_cache_; + + bool is_mouse_inside_; + + std::shared_ptr<BasicLayoutParams> layout_params_; + Size desired_size_; + }; + + // Find the lowest common ancestor. + // Return nullptr if "left" and "right" are not in the same tree. + Control* FindLowestCommonAncestor(Control* left, Control* right); + + // Return the ancestor if one control is the ancestor of the other one, otherwise nullptr. + Control* IsAncestorOrDescendant(Control* left, Control* right); + } +} diff --git a/CruUI/ui/events/ui_event.cpp b/CruUI/ui/events/ui_event.cpp new file mode 100644 index 00000000..59623bab --- /dev/null +++ b/CruUI/ui/events/ui_event.cpp @@ -0,0 +1,19 @@ +#include "ui_event.h" + +#include "ui/control.h" + +namespace cru +{ + namespace ui + { + namespace events + { + Point MouseEventArgs::GetPoint(Control* control) const + { + if (point_.has_value()) + return control->AbsoluteToLocal(point_.value()); + return Point(); + } + } + } +} diff --git a/CruUI/ui/events/ui_event.h b/CruUI/ui/events/ui_event.h new file mode 100644 index 00000000..efb0479b --- /dev/null +++ b/CruUI/ui/events/ui_event.h @@ -0,0 +1,180 @@ +#pragma once + +#include "system_headers.h" +#include <optional> + +#include "base.h" +#include "cru_event.h" +#include "ui/ui_base.h" + +namespace cru +{ + namespace ui + { + class Control; + + namespace events + { + class UiEventArgs : public BasicEventArgs + { + public: + UiEventArgs(Object* sender, Object* original_sender) + : BasicEventArgs(sender), original_sender_(original_sender) + { + + } + + UiEventArgs(const UiEventArgs& other) = default; + UiEventArgs(UiEventArgs&& other) = default; + UiEventArgs& operator=(const UiEventArgs& other) = default; + UiEventArgs& operator=(UiEventArgs&& other) = default; + ~UiEventArgs() override = default; + + Object* GetOriginalSender() const + { + return original_sender_; + } + + private: + Object* original_sender_; + }; + + + class MouseEventArgs : public UiEventArgs + { + public: + MouseEventArgs(Object* sender, Object* original_sender, const std::optional<Point>& point = std::nullopt) + : UiEventArgs(sender, original_sender), point_(point) + { + + } + MouseEventArgs(const MouseEventArgs& other) = default; + MouseEventArgs(MouseEventArgs&& other) = default; + MouseEventArgs& operator=(const MouseEventArgs& other) = default; + MouseEventArgs& operator=(MouseEventArgs&& other) = default; + ~MouseEventArgs() override = default; + + Point GetPoint(Control* control) const; + + private: + std::optional<Point> point_; + }; + + + class MouseButtonEventArgs : public MouseEventArgs + { + public: + MouseButtonEventArgs(Object* sender, Object* original_sender, const Point& point, const MouseButton button) + : MouseEventArgs(sender, original_sender, point), button_(button) + { + + } + MouseButtonEventArgs(const MouseButtonEventArgs& other) = default; + MouseButtonEventArgs(MouseButtonEventArgs&& other) = default; + MouseButtonEventArgs& operator=(const MouseButtonEventArgs& other) = default; + MouseButtonEventArgs& operator=(MouseButtonEventArgs&& other) = default; + ~MouseButtonEventArgs() override = default; + + MouseButton GetMouseButton() const + { + return button_; + } + + private: + MouseButton button_; + }; + + + class DrawEventArgs : public UiEventArgs + { + public: + DrawEventArgs(Object* sender, Object* original_sender, ID2D1DeviceContext* device_context) + : UiEventArgs(sender, original_sender), device_context_(device_context) + { + + } + DrawEventArgs(const DrawEventArgs& other) = default; + DrawEventArgs(DrawEventArgs&& other) = default; + DrawEventArgs& operator=(const DrawEventArgs& other) = default; + DrawEventArgs& operator=(DrawEventArgs&& other) = default; + ~DrawEventArgs() = default; + + ID2D1DeviceContext* GetDeviceContext() const + { + return device_context_; + } + + private: + ID2D1DeviceContext * device_context_; + }; + + + class PositionChangedEventArgs : public UiEventArgs + { + public: + PositionChangedEventArgs(Object* sender, Object* original_sender, const Point& old_position, const Point& new_position) + : UiEventArgs(sender, original_sender), old_position_(old_position), new_position_(new_position) + { + + } + PositionChangedEventArgs(const PositionChangedEventArgs& other) = default; + PositionChangedEventArgs(PositionChangedEventArgs&& other) = default; + PositionChangedEventArgs& operator=(const PositionChangedEventArgs& other) = default; + PositionChangedEventArgs& operator=(PositionChangedEventArgs&& other) = default; + ~PositionChangedEventArgs() override = default; + + Point GetOldPosition() const + { + return old_position_; + } + + Point GetNewPosition() const + { + return new_position_; + } + + private: + Point old_position_; + Point new_position_; + }; + + + class SizeChangedEventArgs : public UiEventArgs + { + public: + SizeChangedEventArgs(Object* sender, Object* original_sender, const Size& old_size, const Size& new_size) + : UiEventArgs(sender, original_sender), old_size_(old_size), new_size_(new_size) + { + + } + SizeChangedEventArgs(const SizeChangedEventArgs& other) = default; + SizeChangedEventArgs(SizeChangedEventArgs&& other) = default; + SizeChangedEventArgs& operator=(const SizeChangedEventArgs& other) = default; + SizeChangedEventArgs& operator=(SizeChangedEventArgs&& other) = default; + ~SizeChangedEventArgs() override = default; + + Size GetOldSize() const + { + return old_size_; + } + + Size GetNewSize() const + { + return new_size_; + } + + private: + Size old_size_; + Size new_size_; + }; + + + using UiEvent = Event<UiEventArgs>; + using MouseEvent = Event<MouseEventArgs>; + using MouseButtonEvent = Event<MouseButtonEventArgs>; + using DrawEvent = Event<DrawEventArgs>; + using PositionChangedEvent = Event<PositionChangedEventArgs>; + using SizeChangedEvent = Event<SizeChangedEventArgs>; + } + } +} diff --git a/CruUI/ui/layout_base.h b/CruUI/ui/layout_base.h new file mode 100644 index 00000000..9bbbc9fd --- /dev/null +++ b/CruUI/ui/layout_base.h @@ -0,0 +1,96 @@ +#pragma once + +#include <optional> + +namespace cru +{ + namespace ui + { + enum class MeasureMode + { + Exactly, + Content, + Stretch + }; + + struct MeasureLength final + { + explicit MeasureLength(const float length = 0.0, const MeasureMode mode = MeasureMode::Exactly) + : length(length), mode(mode) + { + + } + + bool Validate() const + { + return !(mode == MeasureMode::Exactly && length < 0.0); + } + + float length; + MeasureMode mode; + }; + + struct MeasureSize final + { + MeasureLength width; + MeasureLength height; + + bool Validate() const + { + return width.Validate() && height.Validate(); + } + }; + + struct OptionalSize final + { + OptionalSize() + : width(std::nullopt), height(std::nullopt) + { + + } + + OptionalSize(const std::optional<float> width, const std::optional<float> height) + : width(width), height(height) + { + + } + + OptionalSize(const OptionalSize& other) = default; + OptionalSize(OptionalSize&& other) = default; + OptionalSize& operator = (const OptionalSize& other) = default; + OptionalSize& operator = (OptionalSize&& other) = default; + ~OptionalSize() = default; + + bool Validate() const + { + if (width.has_value() && width.value() < 0.0) + return false; + if (height.has_value() && height.value() < 0.0) + return false; + return true; + } + + std::optional<float> width; + std::optional<float> height; + }; + + struct BasicLayoutParams + { + BasicLayoutParams() = default; + BasicLayoutParams(const BasicLayoutParams&) = default; + BasicLayoutParams(BasicLayoutParams&&) = default; + BasicLayoutParams& operator = (const BasicLayoutParams&) = default; + BasicLayoutParams& operator = (BasicLayoutParams&&) = default; + virtual ~BasicLayoutParams() = default; + + bool Validate() const + { + return size.Validate() && max_size.Validate() && min_size.Validate(); + } + + MeasureSize size; + OptionalSize min_size; + OptionalSize max_size; + }; + } +}
\ No newline at end of file diff --git a/CruUI/ui/ui_base.cpp b/CruUI/ui/ui_base.cpp new file mode 100644 index 00000000..b30f65ab --- /dev/null +++ b/CruUI/ui/ui_base.cpp @@ -0,0 +1,8 @@ +#include "ui_base.h" + +namespace cru { + namespace ui { + const Point Point::zero(0, 0); + const Size Size::zero(0, 0); + } +} diff --git a/CruUI/ui/ui_base.h b/CruUI/ui/ui_base.h new file mode 100644 index 00000000..8dfbf53d --- /dev/null +++ b/CruUI/ui/ui_base.h @@ -0,0 +1,105 @@ +#pragma once + +namespace cru +{ + namespace ui + { + struct Point + { + static const Point zero; + + Point() = default; + Point(const float x, const float y) : x(x), y(y) { } + + float x; + float y; + }; + + struct Size + { + static const Size zero; + + Size() = default; + Size(const float width, const float height) : width(width), height(height) { } + + float width; + float height; + }; + + struct Rect + { + Rect() = default; + Rect(const float left, const float top, const float width, const float height) + : left(left), top(top), width(width), height(height) { } + Rect(const Point& lefttop, const Size& size) + : left(lefttop.x), top(lefttop.y), width(size.width), height(size.height) { } + + static Rect FromVertices(const float left, const float top, const float right, const float bottom) + { + return Rect(left, top, right - left, bottom - top); + } + + float GetRight() const + { + return left + width; + } + + float GetBottom() const + { + return top + height; + } + + Point GetLeftTop() const + { + return Point(left, top); + } + + Point GetRightBottom() const + { + return Point(left + width, top + height); + } + + Size GetSize() const + { + return Size(width, height); + } + + bool IsPointInside(const Point& point) const + { + return + point.x >= left && + point.x < GetRight() && + point.y >= top && + point.y < GetBottom(); + } + + float left = 0.0f; + float top = 0.0f; + float width = 0.0f; + float height = 0.0f; + }; + + struct Thickness + { + Thickness() : Thickness(0) { } + explicit Thickness(const float width) + : left(width), top(width), right(width), bottom(width) { } + + Thickness(const float left, const float top, const float right, const float bottom) + : left(left), top(top), right(right), bottom(bottom) { } + + + float left; + float top; + float right; + float bottom; + }; + + enum class MouseButton + { + Left, + Right, + Middle + }; + } +} diff --git a/CruUI/ui/window.cpp b/CruUI/ui/window.cpp new file mode 100644 index 00000000..22d97696 --- /dev/null +++ b/CruUI/ui/window.cpp @@ -0,0 +1,545 @@ +#include "window.h" + +#include "application.h" +#include "graph/graph.h" +#include "exception.h" + +namespace cru +{ + namespace ui + { + WindowClass::WindowClass(const std::wstring& name, WNDPROC window_proc, HINSTANCE hinstance) + : name_(name) + { + WNDCLASSEX window_class; + window_class.cbSize = sizeof(WNDCLASSEX); + + window_class.style = CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS; + window_class.lpfnWndProc = window_proc; + window_class.cbClsExtra = 0; + window_class.cbWndExtra = 0; + window_class.hInstance = hinstance; + window_class.hIcon = LoadIcon(NULL, IDI_APPLICATION); + window_class.hCursor = LoadCursor(NULL, IDC_ARROW); + window_class.hbrBackground = GetSysColorBrush(COLOR_BTNFACE); + window_class.lpszMenuName = NULL; + window_class.lpszClassName = name.c_str(); + window_class.hIconSm = NULL; + + atom_ = RegisterClassEx(&window_class); + if (atom_ == 0) + throw std::runtime_error("Failed to create window class."); + } + + WindowClass::~WindowClass() + { + + } + + const wchar_t * WindowClass::GetName() { + return name_.c_str(); + } + + ATOM WindowClass::GetAtom() { + return atom_; + } + + LRESULT __stdcall GeneralWndProc(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam) { + auto window = Application::GetInstance()->GetWindowManager()->FromHandle(hWnd); + + LRESULT result; + if (window != nullptr && window->HandleWindowMessage(hWnd, Msg, wParam, lParam, result)) + return result; + + return DefWindowProc(hWnd, Msg, wParam, lParam); + } + + WindowManager::WindowManager() { + general_window_class_ = std::make_unique<WindowClass>( + L"CruUIWindowClass", + GeneralWndProc, + Application::GetInstance()->GetInstanceHandle() + ); + } + + void WindowManager::RegisterWindow(HWND hwnd, Window * window) { + const auto find_result = window_map_.find(hwnd); + if (find_result != window_map_.end()) + throw std::runtime_error("The hwnd is already in the map."); + + window_map_.emplace(hwnd, window); + } + + void WindowManager::UnregisterWindow(HWND hwnd) { + const auto find_result = window_map_.find(hwnd); + if (find_result == window_map_.end()) + throw std::runtime_error("The hwnd is not in the map."); + window_map_.erase(find_result); + + if (window_map_.empty()) + Application::GetInstance()->Quit(0); + } + + Window* WindowManager::FromHandle(HWND hwnd) { + const auto find_result = window_map_.find(hwnd); + if (find_result == window_map_.end()) + return nullptr; + else + return find_result->second; + } + + WindowLayoutManager::WindowLayoutManager() + { + } + + WindowLayoutManager::~WindowLayoutManager() + { + } + + void WindowLayoutManager::InvalidateControlPositionCache(Control * control) + { + if (cache_invalid_controls_.count(control) == 1) + return; + + // find descendant then erase it; find ancestor then just return. + for (auto i = cache_invalid_controls_.cbegin(); i != cache_invalid_controls_.cend(); ++i) + { + if (IsAncestorOrDescendant(*i, control) == control) + cache_invalid_controls_.erase(i); + else + return; // find a ancestor of "control", just return + } + + cache_invalid_controls_.insert(control); + + if (cache_invalid_controls_.size() == 1) // when insert just now and not repeat to "InvokeLater". + { + InvokeLater([this] { + RefreshInvalidControlPositionCache(); + }); + } + } + + void WindowLayoutManager::RefreshInvalidControlPositionCache() + { + for (auto i : cache_invalid_controls_) + RefreshControlPositionCache(i); + cache_invalid_controls_.clear(); + } + + void WindowLayoutManager::RefreshControlPositionCache(Control * control) + { + Point point = Point::zero; + auto parent = control; + while ((parent = parent->GetParent())) { + const auto p = parent->GetPositionRelative(); + point.x += p.x; + point.y += p.y; + } + RefreshControlPositionCacheInternal(control, point); + } + + void WindowLayoutManager::RefreshControlPositionCacheInternal(Control * control, const Point & parent_lefttop_absolute) + { + const auto position = control->GetPositionRelative(); + Point lefttop( + parent_lefttop_absolute.x + position.x, + parent_lefttop_absolute.y + position.x + ); + control->position_cache_.lefttop_position_absolute = lefttop; + control->ForeachChild([lefttop](Control* c) { + RefreshControlPositionCacheInternal(c, lefttop); + }); + } + + Window::Window() : layout_manager_(new WindowLayoutManager()), control_list_({ this }) { + auto app = Application::GetInstance(); + hwnd_ = CreateWindowEx(0, + app->GetWindowManager()->GetGeneralWindowClass()->GetName(), + L"", WS_OVERLAPPEDWINDOW, + CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, + nullptr, nullptr, app->GetInstanceHandle(), nullptr + ); + + if (hwnd_ == nullptr) + throw std::runtime_error("Failed to create window."); + + app->GetWindowManager()->RegisterWindow(hwnd_, this); + + render_target_ = app->GetGraphManager()->CreateWindowRenderTarget(hwnd_); + } + + Window::~Window() { + Close(); + } + + WindowLayoutManager* Window::GetLayoutManager() + { + return layout_manager_.get(); + } + + HWND Window::GetWindowHandle() + { + return hwnd_; + } + + bool Window::IsWindowValid() { + return hwnd_ != nullptr; + } + + void Window::Close() { + if (IsWindowValid()) + DestroyWindow(hwnd_); + } + + void Window::Repaint() { + if (IsWindowValid()) { + InvalidateRect(hwnd_, nullptr, false); + UpdateWindow(hwnd_); + } + } + + void Window::Show() { + if (IsWindowValid()) { + ShowWindow(hwnd_, SW_SHOWNORMAL); + } + } + + void Window::Hide() { + if (IsWindowValid()) { + ShowWindow(hwnd_, SW_HIDE); + } + } + + Size Window::GetClientSize() { + if (!IsWindowValid()) + return Size(); + + const auto pixel_rect = GetClientRectPixel(); + return Size( + graph::PixelToDipX(pixel_rect.right), + graph::PixelToDipY(pixel_rect.bottom) + ); + } + + void Window::SetClientSize(const Size & size) { + if (IsWindowValid()) { + const auto window_style = static_cast<DWORD>(GetWindowLongPtr(hwnd_, GWL_STYLE)); + const auto window_ex_style = static_cast<DWORD>(GetWindowLongPtr(hwnd_, GWL_EXSTYLE)); + + RECT rect; + rect.left = 0; + rect.top = 0; + rect.right = graph::DipToPixelX(size.width); + rect.bottom = graph::DipToPixelY(size.height); + AdjustWindowRectEx(&rect, window_style, FALSE, window_ex_style); + + SetWindowPos( + hwnd_, nullptr, 0, 0, + rect.right - rect.left, + rect.bottom - rect.top, + SWP_NOZORDER | SWP_NOMOVE + ); + } + } + + Rect Window::GetWindowRect() { + if (!IsWindowValid()) + return Rect(); + + RECT rect; + ::GetWindowRect(hwnd_, &rect); + + return Rect::FromVertices( + graph::PixelToDipX(rect.left), + graph::PixelToDipY(rect.top), + graph::PixelToDipX(rect.right), + graph::PixelToDipY(rect.bottom) + ); + } + + void Window::SetWindowRect(const Rect & rect) { + if (IsWindowValid()) { + SetWindowPos( + hwnd_, nullptr, + graph::DipToPixelX(rect.left), + graph::DipToPixelY(rect.top), + graph::DipToPixelX(rect.GetRight()), + graph::DipToPixelY(rect.GetBottom()), + SWP_NOZORDER + ); + } + } + + bool Window::HandleWindowMessage(HWND hwnd, int msg, WPARAM w_param, LPARAM l_param, LRESULT & result) { + switch (msg) { + case WM_PAINT: + OnPaintInternal(); + result = 0; + return true; + case WM_ERASEBKGND: + result = 1; + return true; + case WM_SETFOCUS: + OnSetFocusInternal(); + result = 0; + return true; + case WM_KILLFOCUS: + OnKillFocusInternal(); + result = 0; + return true; + case WM_LBUTTONDOWN: + { + POINT point; + point.x = GET_X_LPARAM(l_param); + point.y = GET_Y_LPARAM(l_param); + OnMouseDownInternal(MouseButton::Left, point); + result = 0; + return true; + } + case WM_LBUTTONUP: + { + POINT point; + point.x = GET_X_LPARAM(l_param); + point.y = GET_Y_LPARAM(l_param); + OnMouseDownInternal(MouseButton::Left, point); + result = 0; + return true; + } + case WM_RBUTTONDOWN: + { + POINT point; + point.x = GET_X_LPARAM(l_param); + point.y = GET_Y_LPARAM(l_param); + OnMouseDownInternal(MouseButton::Right, point); + result = 0; + return true; + } + case WM_RBUTTONUP: + { + POINT point; + point.x = GET_X_LPARAM(l_param); + point.y = GET_Y_LPARAM(l_param); + OnMouseDownInternal(MouseButton::Right, point); + result = 0; + return true; + } + case WM_MBUTTONDOWN: + { + POINT point; + point.x = GET_X_LPARAM(l_param); + point.y = GET_Y_LPARAM(l_param); + OnMouseDownInternal(MouseButton::Middle, point); + result = 0; + return true; + } + case WM_MBUTTONUP: + { + POINT point; + point.x = GET_X_LPARAM(l_param); + point.y = GET_Y_LPARAM(l_param); + OnMouseDownInternal(MouseButton::Middle, point); + result = 0; + return true; + } + case WM_SIZE: + OnResizeInternal(LOWORD(l_param), HIWORD(l_param)); + result = 0; + return true; + case WM_DESTROY: + OnDestroyInternal(); + result = 0; + return true; + default: + return false; + } + } + + Point Window::GetPositionRelative() + { + return Point(); + } + + void Window::SetPositionRelative(const Point & position) + { + + } + + Size Window::GetSize() + { + return GetClientSize(); + } + + void Window::SetSize(const Size & size) + { + SetClientSize(size); + } + + void Window::RefreshControlList() { + control_list_.clear(); + TraverseDescendants([this](Control* control) { + this->control_list_.push_back(control); + }); + } + + Control * Window::HitTest(const Point & point) + { + for (auto i = control_list_.crbegin(); i != control_list_.crend(); ++i) { + auto control = *i; + if (control->IsPointInside(control->AbsoluteToLocal(point))) { + return control; + } + } + return nullptr; + } + + bool Window::RequestFocusFor(Control * control) + { + if (control == nullptr) + throw std::invalid_argument("The control to request focus can't be null. You can set it as the window."); + + if (!IsWindowValid()) + return false; + + if (!window_focus_) + { + ::SetFocus(hwnd_); + focus_control_ = control; + return true; // event dispatch will be done in window message handling function "OnSetFocusInternal". + } + + if (focus_control_ == control) + return true; + + DispatchEvent(focus_control_, &Control::OnLoseFocusCore, nullptr); + + focus_control_ = control; + + DispatchEvent(control, &Control::OnGetFocusCore, nullptr); + + return true; + } + + Control* Window::GetFocusControl() + { + return focus_control_; + } + + RECT Window::GetClientRectPixel() { + RECT rect{ }; + GetClientRect(hwnd_, &rect); + return rect; + } + + void Window::OnDestroyInternal() { + Application::GetInstance()->GetWindowManager()->UnregisterWindow(hwnd_); + hwnd_ = nullptr; + } + + void Window::OnPaintInternal() { + render_target_->SetAsTarget(); + + auto device_context = render_target_->GetD2DDeviceContext(); + + device_context->BeginDraw(); + + //Clear the background. + device_context->Clear(D2D1::ColorF(D2D1::ColorF::White)); + + Draw(device_context.Get()); + + ThrowIfFailed( + device_context->EndDraw(), "Failed to draw window." + ); + + render_target_->Present(); + + ValidateRect(hwnd_, nullptr); + } + + void Window::OnResizeInternal(int new_width, int new_height) { + render_target_->ResizeBuffer(new_width, new_height); + } + + void Window::OnSetFocusInternal() + { + window_focus_ = true; + if (focus_control_ != nullptr) + DispatchEvent(focus_control_, &Control::OnGetFocusCore, nullptr); + } + + void Window::OnKillFocusInternal() + { + window_focus_ = false; + if (focus_control_ != nullptr) + DispatchEvent(focus_control_, &Control::OnLoseFocusCore, nullptr); + } + + void Window::OnMouseMoveInternal(POINT point) + { + Point dip_point( + graph::PixelToDipX(point.x), + graph::PixelToDipY(point.y) + ); + + //when mouse was previous outside the window + if (mouse_hover_control_ == nullptr) { + //invoke TrackMouseEvent to have WM_MOUSELEAVE sent. + TRACKMOUSEEVENT tme; + tme.cbSize = sizeof tme; + tme.dwFlags = TME_LEAVE; + tme.hwndTrack = hwnd_; + + TrackMouseEvent(&tme); + } + + //Find the first control that hit test succeed. + const auto new_control_mouse_hover = HitTest(dip_point); + + if (new_control_mouse_hover != mouse_hover_control_) //if the mouse-hover-on control changed + { + const auto lowest_common_ancestor = FindLowestCommonAncestor(mouse_hover_control_, new_control_mouse_hover); + if (mouse_hover_control_ != nullptr) // if last mouse-hover-on control exists + { + // dispatch mouse leave event. + DispatchEvent(mouse_hover_control_, &Control::OnMouseLeaveCore, lowest_common_ancestor); + } + mouse_hover_control_ = new_control_mouse_hover; + // dispatch mouse enter event. + DispatchEvent(new_control_mouse_hover, &Control::OnMouseEnterCore, lowest_common_ancestor, dip_point); + } + + DispatchEvent(new_control_mouse_hover, &Control::OnMouseMoveCore, nullptr, dip_point); + } + + void Window::OnMouseLeaveInternal() + { + DispatchEvent(mouse_hover_control_, &Control::OnMouseLeaveCore, nullptr); + mouse_hover_control_ = nullptr; + } + + void Window::OnMouseDownInternal(MouseButton button, POINT point) + { + Point dip_point( + graph::PixelToDipX(point.x), + graph::PixelToDipY(point.y) + ); + + const auto control = HitTest(dip_point); + + DispatchEvent(control, &Control::OnMouseDownCore, nullptr, dip_point, button); + } + + void Window::OnMouseUpInternal(MouseButton button, POINT point) + { + Point dip_point( + graph::PixelToDipX(point.x), + graph::PixelToDipY(point.y) + ); + + const auto control = HitTest(dip_point); + + DispatchEvent(control, &Control::OnMouseUpCore, nullptr, dip_point, button); + } + } +} diff --git a/CruUI/ui/window.h b/CruUI/ui/window.h new file mode 100644 index 00000000..1cb6b5f7 --- /dev/null +++ b/CruUI/ui/window.h @@ -0,0 +1,257 @@ +#pragma once + +#include "system_headers.h" +#include <set> +#include <map> +#include <list> +#include <memory> + +#include "Control.h" + +namespace cru { + namespace graph { + class WindowRenderTarget; + } + + namespace ui { + class WindowClass : public Object + { + public: + WindowClass(const std::wstring& name, WNDPROC window_proc, HINSTANCE h_instance); + WindowClass(const WindowClass& other) = delete; + WindowClass(WindowClass&& other) = delete; + WindowClass& operator=(const WindowClass& other) = delete; + WindowClass& operator=(WindowClass&& other) = delete; + ~WindowClass() override; + + + const wchar_t* GetName(); + ATOM GetAtom(); + + private: + std::wstring name_; + ATOM atom_; + }; + + class WindowManager : public Object + { + public: + WindowManager(); + WindowManager(const WindowManager& other) = delete; + WindowManager(WindowManager&& other) = delete; + WindowManager& operator=(const WindowManager& other) = delete; + WindowManager& operator=(WindowManager&& other) = delete; + ~WindowManager() override = default; + + + //Get the general window class for creating ordinary window. + WindowClass* GetGeneralWindowClass() const + { + return general_window_class_.get(); + } + + //Register a window newly created. + //This function adds the hwnd to hwnd-window map. + //It should be called immediately after a window was created. + void RegisterWindow(HWND hwnd, Window* window); + + //Unregister a window that is going to be destroyed. + //This function removes the hwnd from the hwnd-window map. + //It should be called immediately before a window is going to be destroyed, + void UnregisterWindow(HWND hwnd); + + //Return a pointer to the Window object related to the HWND or nullptr if the hwnd is not in the map. + Window* FromHandle(HWND hwnd); + + private: + std::unique_ptr<WindowClass> general_window_class_; + std::map<HWND, Window*> window_map_; + }; + + + class WindowLayoutManager : public Object + { + public: + WindowLayoutManager(); + WindowLayoutManager(const WindowLayoutManager& other) = delete; + WindowLayoutManager(WindowLayoutManager&& other) = delete; + WindowLayoutManager& operator=(const WindowLayoutManager& other) = delete; + WindowLayoutManager& operator=(WindowLayoutManager&& other) = delete; + ~WindowLayoutManager() override; + + //Mark position cache of the control and its descendants invalid, + //(which is saved as an auto-managed list internal) + //and send a message to refresh them. + void InvalidateControlPositionCache(Control* control); + + //Refresh position cache of the control and its descendants whose cache + //has been marked as invalid. + void RefreshInvalidControlPositionCache(); + + //Refresh position cache of the control and its descendants immediately. + static void RefreshControlPositionCache(Control* control); + + private: + static void RefreshControlPositionCacheInternal(Control* control, const Point& parent_lefttop_absolute); + + private: + std::set<Control*> cache_invalid_controls_; + }; + + class Window : public Control + { + friend class WindowManager; + public: + Window(); + Window(const Window& other) = delete; + Window(Window&& other) = delete; + Window& operator=(const Window& other) = delete; + Window& operator=(Window&& other) = delete; + ~Window() override; + + public: + //*************** region: managers *************** + WindowLayoutManager* GetLayoutManager(); + + + //*************** region: handle *************** + + //Get the handle of the window. Return null if window is invalid. + HWND GetWindowHandle(); + + //Return if the window is still valid, that is, hasn't been closed or destroyed. + bool IsWindowValid(); + + + //*************** region: window operations *************** + + //Close and destroy the window if the window is valid. + void Close(); + + //Send a repaint message to the window's message queue which may make the window repaint. + void Repaint(); + + //Show the window. + void Show(); + + //Hide thw window. + void Hide(); + + //Get the client size. + Size GetClientSize(); + + //Set the client size and repaint. + void SetClientSize(const Size& size); + + //Get the rect of the window containing frame. + //The lefttop of the rect is relative to screen lefttop. + Rect GetWindowRect(); + + //Set the rect of the window containing frame. + //The lefttop of the rect is relative to screen lefttop. + void SetWindowRect(const Rect& rect); + + //Handle the raw window message. + //Return true if the message is handled and get the result through "result" argument. + //Return false if the message is not handled. + bool HandleWindowMessage(HWND hwnd, int msg, WPARAM w_param, LPARAM l_param, LRESULT& result); + + + //*************** region: position and size *************** + + //Always return (0, 0) for a window. + Point GetPositionRelative() override final; + + //This method has no effect for a window. + void SetPositionRelative(const Point& position) override final; + + //Get the size of client area for a window. + Size GetSize() override final; + + //Set the size of client area for a window. + void SetSize(const Size& size) override final; + + + //*************** region: features *************** + + //Refresh control list. + //It should be invoked every time a control is added or removed from the tree. + void RefreshControlList(); + + //Get the most top control at "point". + Control* HitTest(const Point& point); + + + //*************** region: focus *************** + + //Request focus for specified control. + bool RequestFocusFor(Control* control); + + //Get the control that has focus. + Control* GetFocusControl(); + + + private: + //*************** region: native operations *************** + + //Get the client rect in pixel. + RECT GetClientRectPixel(); + + + //*************** region: native messages *************** + + void OnDestroyInternal(); + void OnPaintInternal(); + void OnResizeInternal(int new_width, int new_height); + + void OnSetFocusInternal(); + void OnKillFocusInternal(); + + void OnMouseMoveInternal(POINT point); + void OnMouseLeaveInternal(); + void OnMouseDownInternal(MouseButton button, POINT point); + void OnMouseUpInternal(MouseButton button, POINT point); + + + + //*************** region: event dispatcher helper *************** + + template<typename EventArgs> + using EventMethod = void (Control::*)(EventArgs&); + + // Dispatch the event. + // + // This will invoke the "event_method" of the control and its parent and parent's + // parent ... (until "last_receiver" if it's not nullptr) with appropriate args. + // + // Args is of type "EventArgs". The first init argument is "sender", which is + // automatically bound to each receiving control. The second init argument is + // "original_sender", which is unchanged. And "args" will be perfectly forwarded + // as the rest arguments. + template<typename EventArgs, typename... Args> + void DispatchEvent(Control* original_sender, EventMethod<EventArgs> event_method, Control* last_receiver, Args&&... args) + { + auto control = original_sender; + while (control != nullptr && control != last_receiver) + { + EventArgs event_args(control, original_sender, std::forward<Args>(args)...); + (control->*event_method)(event_args); + control = control->GetParent(); + } + } + + private: + std::unique_ptr<WindowLayoutManager> layout_manager_; + + HWND hwnd_ = nullptr; + std::shared_ptr<graph::WindowRenderTarget> render_target_{}; + + std::list<Control*> control_list_{}; + + Control* mouse_hover_control_ = nullptr; + + bool window_focus_ = false; + Control* focus_control_ = this; // "focus_control_" can't be nullptr + }; + } +} diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..989e2c59 --- /dev/null +++ b/LICENSE @@ -0,0 +1,201 @@ +Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License.
\ No newline at end of file diff --git a/Layout Rules.md b/Layout Rules.md new file mode 100644 index 00000000..3bbfdac3 --- /dev/null +++ b/Layout Rules.md @@ -0,0 +1,95 @@ +# Layout Specifications + +## overview +This document is the specification of layout system. + +The layout system imitates WPF and Android layout system. + +## rules + +### about `width` and `height` in `LayoutParams` +There is three mode in measure: `Content`, `Exactly`, `Stretch`. + +- `Exactly` means the control should be of an exact size. + +- `Content` means the control has the size that contains the children. + +- `Stretch` means the control stretch to the max size that it can fill. If parent is `Content`, it occupies all available room parent provides, which means its parent will stretch as well. + +### about `max_size`, `min_size` + +`max_size` specifies the max size and `min_size` specifies the min size. They are of higher priority of `width` and `height`. Calculated size should be adjusted according to the four properties. + +## structure + +### enum `MeasureMode` +``` c++ +enum class MeasureMode +{ + Content, + Stretch, + Exactly +}; +``` + +### struct `MeasureLength` +``` c++ +struct MeaureLength +{ + float length; + MeasureMode mode; +}; +``` + +### struct `MeaureSize` +``` c++ +struct MeasureSize +{ + MeasureLength width; + MeasureLength height; +}; +``` + +### struct `OptionalSize` +``` c++ +struct OptionalSize +{ + optional<float> width; + optional<float> height; +} +``` + +### struct `BasicLayoutParams` +``` c++ +struct BasicLayoutParams +{ + MeasureSize size; + OptionalSize max_size; + OptionalSize min_size; +} +``` + +### interface `ILayoutable` +``` c++ +struct ILayoutable : virtual Interface +{ + virtual void Measure(const Size&) = 0; + virtual void Layout(const Rect&) = 0; + + virtual BasicLayoutParams* GetLayoutParams() = 0; + virtual void SetLayoutParams(BasicLayoutParams* params) = 0; + + virtual Size GetDesiredSize() = 0; + virtual void SetDesiredSize(const Size& size) = 0; + +/* +protected: + virtual Size OnMeasure(const Size& size); + virtual void OnLayout(const Rect& rect); +*/ +}; +``` + +## process + + |