aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorcrupest <crupest@outlook.com>2018-09-01 23:28:28 +0800
committercrupest <crupest@outlook.com>2018-09-01 23:28:28 +0800
commit956a401f9c955f26b7e661dc80f76bfc43fc4124 (patch)
tree8af088933c7bc08942478daddd55c92de8668359
downloadcru-956a401f9c955f26b7e661dc80f76bfc43fc4124.tar.gz
cru-956a401f9c955f26b7e661dc80f76bfc43fc4124.tar.bz2
cru-956a401f9c955f26b7e661dc80f76bfc43fc4124.zip
Initial commit
-rw-r--r--.gitattributes2
-rw-r--r--.gitignore324
-rw-r--r--CruUI.sln31
-rw-r--r--CruUI/CruUI.vcxproj183
-rw-r--r--CruUI/CruUI.vcxproj.filters90
-rw-r--r--CruUI/application.cpp96
-rw-r--r--CruUI/application.h75
-rw-r--r--CruUI/base.h42
-rw-r--r--CruUI/cru_event.cpp5
-rw-r--r--CruUI/cru_event.h86
-rw-r--r--CruUI/exception.cpp29
-rw-r--r--CruUI/exception.h42
-rw-r--r--CruUI/global_macros.h3
-rw-r--r--CruUI/graph/graph.cpp233
-rw-r--r--CruUI/graph/graph.h126
-rw-r--r--CruUI/main.cppbin0 -> 3038 bytes
-rw-r--r--CruUI/system_headers.h20
-rw-r--r--CruUI/timer.cpp97
-rw-r--r--CruUI/timer.h49
-rw-r--r--CruUI/ui/control.cpp488
-rw-r--r--CruUI/ui/control.h248
-rw-r--r--CruUI/ui/events/ui_event.cpp19
-rw-r--r--CruUI/ui/events/ui_event.h180
-rw-r--r--CruUI/ui/layout_base.h96
-rw-r--r--CruUI/ui/ui_base.cpp8
-rw-r--r--CruUI/ui/ui_base.h105
-rw-r--r--CruUI/ui/window.cpp545
-rw-r--r--CruUI/ui/window.h257
-rw-r--r--LICENSE201
-rw-r--r--Layout Rules.md95
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
new file mode 100644
index 00000000..185dd90d
--- /dev/null
+++ b/CruUI/main.cpp
Binary files differ
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
+
+