Browse Source

Merge branch 'master' into issues/2106

pull/2134/head
Andrey Kunchev 8 years ago
committed by GitHub
parent
commit
ff22cda810
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 0
      .nuke
  2. 24
      .travis.yml
  3. 6
      Avalonia.sln
  4. 23
      appveyor.yml
  5. 25
      azure-pipelines.yml
  6. 312
      build.cake
  7. 226
      build.ps1
  8. 139
      build.sh
  9. 15
      cake.config
  10. 231
      nukebuild/Build.cs
  11. 140
      nukebuild/BuildParameters.cs
  12. 79
      nukebuild/Shims.cs
  13. 35
      nukebuild/_build.csproj
  14. 24
      nukebuild/_build.csproj.DotSettings
  15. 6
      readme.md
  16. 10
      samples/ControlCatalog.NetCore/Program.cs
  17. 13
      samples/ControlCatalog/DecoratedWindow.xaml
  18. 4
      samples/ControlCatalog/Pages/TextBoxPage.xaml
  19. 9
      src/Avalonia.Controls/AppBuilderBase.cs
  20. 4
      src/Avalonia.Themes.Default/MenuItem.xaml
  21. 16
      src/Avalonia.Visuals/Media/FontFamily.cs
  22. 63
      src/Avalonia.Visuals/Media/Fonts/FontFamilyKey.cs
  23. 94
      src/Avalonia.Visuals/Media/Fonts/FontFamilyLoader.cs
  24. 9
      src/Avalonia.Visuals/Rendering/ImmediateRenderer.cs
  25. 1
      src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj
  26. 9
      src/Markup/Avalonia.Markup.Xaml/AvaloniaTypeConverters.cs
  27. 28
      src/Markup/Avalonia.Markup.Xaml/Converters/FontFamilyTypeConverter.cs
  28. 57
      src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs
  29. 10
      src/Windows/Avalonia.Win32/PopupImpl.cs
  30. 184
      src/Windows/Avalonia.Win32/WindowImpl.cs
  31. 8
      tests/Avalonia.Visuals.UnitTests/Media/Fonts/FontFamilyKeyTests.cs
  32. 84
      tests/Avalonia.Visuals.UnitTests/Rendering/ImmediateRendererTests.cs

0
.nuke

24
.travis.yml

@ -1,24 +0,0 @@
language: csharp
os:
- linux
dist: trusty
osx_image: xcode8.3
env:
global:
- DOTNET_SKIP_FIRST_TIME_EXPERIENCE=1
- DOTNET_CLI_TELEMETRY_OPTOUT=1
mono:
- 5.2.0
dotnet: 2.1.200
script:
- sudo apt-get update
- sudo apt-get install castxml
- ./build.sh --target "Travis" --configuration "Release"
notifications:
email: false
webhooks:
urls:
- https://webhooks.gitter.im/e/98f653320ef2b7506c05
on_success: change
on_failure: always
on_start: never

6
Avalonia.sln

@ -191,6 +191,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Desktop", "src\Ava
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.Build.Tasks", "src\Avalonia.Build.Tasks\Avalonia.Build.Tasks.csproj", "{BF28998D-072C-439A-AFBB-2FE5021241E0}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.Build.Tasks", "src\Avalonia.Build.Tasks\Avalonia.Build.Tasks.csproj", "{BF28998D-072C-439A-AFBB-2FE5021241E0}"
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "_build", "nukebuild\_build.csproj", "{3F00BC43-5095-477F-93D8-E65B08179A00}"
EndProject
Global Global
GlobalSection(SharedMSBuildProjectFiles) = preSolution GlobalSection(SharedMSBuildProjectFiles) = preSolution
src\Shared\RenderHelpers\RenderHelpers.projitems*{3c4c0cb4-0c0f-4450-a37b-148c84ff905f}*SharedItemsImports = 13 src\Shared\RenderHelpers\RenderHelpers.projitems*{3c4c0cb4-0c0f-4450-a37b-148c84ff905f}*SharedItemsImports = 13
@ -216,6 +218,10 @@ Global
Release|iPhoneSimulator = Release|iPhoneSimulator Release|iPhoneSimulator = Release|iPhoneSimulator
EndGlobalSection EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution GlobalSection(ProjectConfigurationPlatforms) = postSolution
{3F00BC43-5095-477F-93D8-E65B08179A00}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{3F00BC43-5095-477F-93D8-E65B08179A00}.Release|Any CPU.ActiveCfg = Release|Any CPU
{3F00BC43-5095-477F-93D8-E65B08179A00}.Debug|Any CPU.Build.0 = Debug|Any CPU
{3F00BC43-5095-477F-93D8-E65B08179A00}.Release|Any CPU.Build.0 = Release|Any CPU
{B09B78D8-9B26-48B0-9149-D64A2F120F3F}.Ad-Hoc|Any CPU.ActiveCfg = Release|Any CPU {B09B78D8-9B26-48B0-9149-D64A2F120F3F}.Ad-Hoc|Any CPU.ActiveCfg = Release|Any CPU
{B09B78D8-9B26-48B0-9149-D64A2F120F3F}.Ad-Hoc|Any CPU.Build.0 = Release|Any CPU {B09B78D8-9B26-48B0-9149-D64A2F120F3F}.Ad-Hoc|Any CPU.Build.0 = Release|Any CPU
{B09B78D8-9B26-48B0-9149-D64A2F120F3F}.Ad-Hoc|iPhone.ActiveCfg = Release|Any CPU {B09B78D8-9B26-48B0-9149-D64A2F120F3F}.Ad-Hoc|iPhone.ActiveCfg = Release|Any CPU

23
appveyor.yml

@ -1,23 +0,0 @@
os: Visual Studio 2017
skip_branch_with_pr: true
configuration:
- Release
environment:
DOTNET_SKIP_FIRST_TIME_EXPERIENCE: 1
DOTNET_CLI_TELEMETRY_OPTOUT: 1
NUGET_API_URL: https://www.nuget.org/api/v2/package
MYGET_API_KEY:
secure: OtVfyN3ErqQrDTnWH2HDfJDlCiu/i4/X4wFmK3ZXXP7HmCiXYPSbTjMPwwdOxRaK
MYGET_API_URL: https://www.myget.org/F/avalonia-ci/api/v2/package
init:
- ps: if (Test-Path env:nuget_address) {[System.IO.File]::AppendAllText("C:\Windows\System32\drivers\etc\hosts", "`n$($env:nuget_address)`tapi.nuget.org")}
before_build:
- git submodule update --init
build_script:
- ps: .\build.ps1 -Target "AppVeyor" -Configuration "$env:configuration"
test: off
artifacts:
- path: artifacts\nuget\*.nupkg
- path: artifacts\zip\*.zip
- path: artifacts\inspectcode.xml

25
azure-pipelines.yml

@ -11,19 +11,18 @@ jobs:
sudo apt-get install castxml sudo apt-get install castxml
- task: CmdLine@2 - task: CmdLine@2
displayName: 'Install Cake' displayName: 'Install Nuke'
inputs: inputs:
script: | script: |
dotnet tool install -g Cake.Tool --version 0.30.0 dotnet tool install --global Nuke.GlobalTool --version 0.12.3
- task: CmdLine@2 - task: CmdLine@2
displayName: 'Run Cake' displayName: 'Run Nuke'
inputs: inputs:
script: | script: |
export PATH="$PATH:$HOME/.dotnet/tools" export PATH="$PATH:$HOME/.dotnet/tools"
dotnet --info dotnet --info
printenv printenv
dotnet cake build.cake -target="Azure-Linux" -configuration="Release" nuke --target CiAzureLinux --configuration=Release
- task: PublishTestResults@2 - task: PublishTestResults@2
inputs: inputs:
@ -55,13 +54,13 @@ jobs:
script: brew install castxml script: brew install castxml
- task: CmdLine@2 - task: CmdLine@2
displayName: 'Install Cake' displayName: 'Install Nuke'
inputs: inputs:
script: | script: |
dotnet tool install -g Cake.Tool --version 0.30.0 dotnet tool install --global Nuke.GlobalTool --version 0.12.3
- task: CmdLine@2 - task: CmdLine@2
displayName: 'Run Cake' displayName: 'Run Nuke'
inputs: inputs:
script: | script: |
export COREHOST_TRACE=0 export COREHOST_TRACE=0
@ -72,7 +71,7 @@ jobs:
export PATH="$PATH:$HOME/.dotnet/tools" export PATH="$PATH:$HOME/.dotnet/tools"
dotnet --info dotnet --info
printenv printenv
dotnet cake build.cake -target="Azure-OSX" -configuration="Release" nuke --target CiAzureOSX --configuration Release
- task: PublishTestResults@2 - task: PublishTestResults@2
inputs: inputs:
@ -97,17 +96,17 @@ jobs:
vmImage: 'vs2017-win2016' vmImage: 'vs2017-win2016'
steps: steps:
- task: CmdLine@2 - task: CmdLine@2
displayName: 'Install Cake' displayName: 'Install Nuke'
inputs: inputs:
script: | script: |
dotnet tool install -g Cake.Tool --version 0.30.0 dotnet tool install --global Nuke.GlobalTool --version 0.12.3
- task: CmdLine@2 - task: CmdLine@2
displayName: 'Run Cake' displayName: 'Run Nuke'
inputs: inputs:
script: | script: |
set PATH=%PATH%;%USERPROFILE%\.dotnet\tools set PATH=%PATH%;%USERPROFILE%\.dotnet\tools
dotnet cake build.cake -target="Azure-Windows" -configuration="Release" nuke --target CiAzureWindows --configuration Release
- task: PublishTestResults@2 - task: PublishTestResults@2
inputs: inputs:

312
build.cake

@ -1,312 +0,0 @@
///////////////////////////////////////////////////////////////////////////////
// TOOLS
///////////////////////////////////////////////////////////////////////////////
#tool "nuget:?package=NuGet.CommandLine&version=4.7.1"
#tool "nuget:?package=JetBrains.ReSharper.CommandLineTools&version=2018.2.3"
#tool "nuget:?package=xunit.runner.console&version=2.3.1"
#tool "nuget:?package=JetBrains.dotMemoryUnit&version=3.0.20171219.105559"
///////////////////////////////////////////////////////////////////////////////
// USINGS
///////////////////////////////////////////////////////////////////////////////
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
///////////////////////////////////////////////////////////////////////////////
// SCRIPTS
///////////////////////////////////////////////////////////////////////////////
#load "./parameters.cake"
///////////////////////////////////////////////////////////////////////////////
// SETUP
///////////////////////////////////////////////////////////////////////////////
Setup<Parameters>(context =>
{
var parameters = new Parameters(context);
Information("Building version {0} of Avalonia ({1}) using version {2} of Cake.",
parameters.Version,
parameters.Configuration,
typeof(ICakeContext).Assembly.GetName().Version.ToString());
if (parameters.IsRunningOnAppVeyor)
{
Information("Repository Name: " + BuildSystem.AppVeyor.Environment.Repository.Name);
Information("Repository Branch: " + BuildSystem.AppVeyor.Environment.Repository.Branch);
}
Information("Target:" + context.TargetTask.Name);
Information("Configuration: " + parameters.Configuration);
Information("IsLocalBuild: " + parameters.IsLocalBuild);
Information("IsRunningOnUnix: " + parameters.IsRunningOnUnix);
Information("IsRunningOnWindows: " + parameters.IsRunningOnWindows);
Information("IsRunningOnAppVeyor: " + parameters.IsRunningOnAppVeyor);
Information("IsRunnongOnAzure:" + parameters.IsRunningOnAzure);
Information("IsPullRequest: " + parameters.IsPullRequest);
Information("IsMainRepo: " + parameters.IsMainRepo);
Information("IsMasterBranch: " + parameters.IsMasterBranch);
Information("IsReleaseBranch: " + parameters.IsReleaseBranch);
Information("IsTagged: " + parameters.IsTagged);
Information("IsReleasable: " + parameters.IsReleasable);
Information("IsMyGetRelease: " + parameters.IsMyGetRelease);
Information("IsNuGetRelease: " + parameters.IsNuGetRelease);
return parameters;
});
///////////////////////////////////////////////////////////////////////////////
// TEARDOWN
///////////////////////////////////////////////////////////////////////////////
Teardown<Parameters>((context, buildContext) =>
{
Information("Finished running tasks.");
});
///////////////////////////////////////////////////////////////////////////////
// TASKS
///////////////////////////////////////////////////////////////////////////////
Task("Clean-Impl")
.Does<Parameters>(data =>
{
CleanDirectories(data.BuildDirs);
CleanDirectory(data.ArtifactsDir);
CleanDirectory(data.NugetRoot);
CleanDirectory(data.ZipRoot);
CleanDirectory(data.TestResultsRoot);
});
void DotNetCoreBuild(Parameters parameters)
{
var settings = new DotNetCoreBuildSettings
{
Configuration = parameters.Configuration,
MSBuildSettings = new DotNetCoreMSBuildSettings
{
Properties =
{
{ "PackageVersion", new [] { parameters.Version } }
}
}
};
DotNetCoreBuild(parameters.MSBuildSolution, settings);
}
Task("Build-Impl")
.Does<Parameters>(data =>
{
if(data.IsRunningOnWindows)
{
MSBuild(data.MSBuildSolution, settings => {
settings.SetConfiguration(data.Configuration);
settings.SetVerbosity(Verbosity.Minimal);
settings.WithProperty("iOSRoslynPathHackRequired", "true");
settings.WithProperty("PackageVersion", data.Version);
settings.UseToolVersion(MSBuildToolVersion.VS2017);
settings.WithRestore();
});
}
else
{
DotNetCoreBuild(data);
}
});
void RunCoreTest(string project, Parameters parameters, bool coreOnly = false)
{
if(!project.EndsWith(".csproj"))
project = System.IO.Path.Combine(project, System.IO.Path.GetFileName(project)+".csproj");
Information("Running tests from " + project);
var frameworks = new List<string>(){"netcoreapp2.0"};
foreach(var fw in frameworks)
{
if(!fw.StartsWith("netcoreapp") && coreOnly)
continue;
Information("Running for " + fw);
var settings = new DotNetCoreTestSettings {
Configuration = parameters.Configuration,
Framework = fw,
NoBuild = true,
NoRestore = true
};
if (parameters.PublishTestResults)
{
settings.Logger = "trx";
settings.ResultsDirectory = parameters.TestResultsRoot;
}
DotNetCoreTest(project, settings);
}
}
Task("Run-Unit-Tests-Impl")
.WithCriteria<Parameters>((context, data) => !data.SkipTests)
.Does<Parameters>(data =>
{
RunCoreTest("./tests/Avalonia.Base.UnitTests", data, false);
RunCoreTest("./tests/Avalonia.Controls.UnitTests", data, false);
RunCoreTest("./tests/Avalonia.Input.UnitTests", data, false);
RunCoreTest("./tests/Avalonia.Interactivity.UnitTests", data, false);
RunCoreTest("./tests/Avalonia.Layout.UnitTests", data, false);
RunCoreTest("./tests/Avalonia.Markup.UnitTests", data, false);
RunCoreTest("./tests/Avalonia.Markup.Xaml.UnitTests", data, false);
RunCoreTest("./tests/Avalonia.Styling.UnitTests", data, false);
RunCoreTest("./tests/Avalonia.Visuals.UnitTests", data, false);
RunCoreTest("./tests/Avalonia.Skia.UnitTests", data, false);
RunCoreTest("./tests/Avalonia.ReactiveUI.UnitTests", data, false);
if (data.IsRunningOnWindows)
{
RunCoreTest("./tests/Avalonia.Direct2D1.UnitTests", data, false);
}
});
Task("Run-Designer-Tests-Impl")
.WithCriteria<Parameters>((context, data) => !data.SkipTests)
.Does<Parameters>(data =>
{
RunCoreTest("./tests/Avalonia.DesignerSupport.Tests", data, false);
});
Task("Run-Render-Tests-Impl")
.WithCriteria<Parameters>((context, data) => !data.SkipTests)
.WithCriteria<Parameters>((context, data) => data.IsRunningOnWindows)
.Does<Parameters>(data =>
{
RunCoreTest("./tests/Avalonia.Skia.RenderTests/Avalonia.Skia.RenderTests.csproj", data, true);
RunCoreTest("./tests/Avalonia.Direct2D1.RenderTests/Avalonia.Direct2D1.RenderTests.csproj", data, true);
});
Task("Run-Leak-Tests-Impl")
.WithCriteria<Parameters>((context, data) => !data.SkipTests)
.WithCriteria<Parameters>((context, data) => data.IsRunningOnWindows)
.Does(() =>
{
var dotMemoryUnit = Context.Tools.Resolve("dotMemoryUnit.exe");
var leakTestsExitCode = StartProcess(dotMemoryUnit, new ProcessSettings
{
Arguments = new ProcessArgumentBuilder()
.Append(Context.Tools.Resolve("xunit.console.x86.exe").FullPath)
.Append("--propagate-exit-code")
.Append("--")
.Append("tests\\Avalonia.LeakTests\\bin\\Release\\net461\\Avalonia.LeakTests.dll"),
Timeout = 120000
});
if (leakTestsExitCode != 0)
{
throw new Exception("Leak Tests failed");
}
});
Task("Zip-Files-Impl")
.Does<Parameters>(data =>
{
Zip(data.BinRoot, data.ZipCoreArtifacts);
Zip(data.NugetRoot, data.ZipNuGetArtifacts);
Zip(data.ZipSourceControlCatalogDesktopDirs,
data.ZipTargetControlCatalogDesktopDirs,
GetFiles(data.ZipSourceControlCatalogDesktopDirs.FullPath + "/*.dll") +
GetFiles(data.ZipSourceControlCatalogDesktopDirs.FullPath + "/*.config") +
GetFiles(data.ZipSourceControlCatalogDesktopDirs.FullPath + "/*.so") +
GetFiles(data.ZipSourceControlCatalogDesktopDirs.FullPath + "/*.dylib") +
GetFiles(data.ZipSourceControlCatalogDesktopDirs.FullPath + "/*.exe"));
});
void DotNetCorePack(Parameters parameters)
{
var settings = new DotNetCorePackSettings
{
Configuration = parameters.Configuration,
MSBuildSettings = new DotNetCoreMSBuildSettings
{
Properties =
{
{ "PackageVersion", new [] { parameters.Version } }
}
}
};
DotNetCorePack(parameters.MSBuildSolution, settings);
}
Task("Create-NuGet-Packages-Impl")
.Does<Parameters>(data =>
{
if(data.IsRunningOnWindows)
{
MSBuild(data.MSBuildSolution, settings => {
settings.SetConfiguration(data.Configuration);
settings.SetVerbosity(Verbosity.Minimal);
settings.WithProperty("iOSRoslynPathHackRequired", "true");
settings.WithProperty("PackageVersion", data.Version);
settings.UseToolVersion(MSBuildToolVersion.VS2017);
settings.WithRestore();
settings.WithTarget("Pack");
});
}
else
{
DotNetCorePack(data);
}
});
///////////////////////////////////////////////////////////////////////////////
// TARGETS
///////////////////////////////////////////////////////////////////////////////
Task("Build")
.IsDependentOn("Clean-Impl")
.IsDependentOn("Build-Impl");
Task("Run-Tests")
.IsDependentOn("Build")
.IsDependentOn("Run-Unit-Tests-Impl")
.IsDependentOn("Run-Render-Tests-Impl")
.IsDependentOn("Run-Designer-Tests-Impl")
.IsDependentOn("Run-Leak-Tests-Impl");
Task("Package")
.IsDependentOn("Run-Tests")
.IsDependentOn("Create-NuGet-Packages-Impl");
Task("AppVeyor")
.IsDependentOn("Package")
.IsDependentOn("Zip-Files-Impl");
Task("Travis")
.IsDependentOn("Run-Tests");
Task("Azure-Linux")
.IsDependentOn("Run-Tests");
Task("Azure-OSX")
.IsDependentOn("Package")
.IsDependentOn("Zip-Files-Impl");
Task("Azure-Windows")
.IsDependentOn("Package")
.IsDependentOn("Zip-Files-Impl");
///////////////////////////////////////////////////////////////////////////////
// EXECUTE
///////////////////////////////////////////////////////////////////////////////
var target = Context.Argument("target", "Default");
if (target == "Default")
{
target = Context.IsRunningOnWindows() ? "Package" : "Run-Tests";
}
RunTarget(target);

226
build.ps1

@ -1,201 +1,69 @@
##########################################################################
# This is the Cake bootstrapper script for PowerShell.
# This file was downloaded from https://github.com/cake-build/resources
# Feel free to change this file to fit your needs.
##########################################################################
<#
.SYNOPSIS
This is a Powershell script to bootstrap a Cake build.
.DESCRIPTION
This Powershell script will download NuGet if missing, restore NuGet tools (including Cake)
and execute your Cake build script with the parameters you provide.
.PARAMETER Script
The build script to execute.
.PARAMETER Target
The build script target to run.
.PARAMETER Platform
The build platform to use.
.PARAMETER Configuration
The build configuration to use.
.PARAMETER Verbosity
Specifies the amount of information to be displayed.
.PARAMETER Experimental
Tells Cake to use the latest Roslyn release.
.PARAMETER WhatIf
Performs a dry run of the build script.
No tasks will be executed.
.PARAMETER Mono
Tells Cake to use the Mono scripting engine.
.PARAMETER SkipToolPackageRestore
Skips restoring of packages.
.PARAMETER SkipTests
Skips unit tests
.PARAMETER ScriptArgs
Remaining arguments are added here.
.LINK
http://cakebuild.net
#>
[CmdletBinding()] [CmdletBinding()]
Param( Param(
[string]$Script = "build.cake", #[switch]$CustomParam,
[string]$Target = "Default",
[ValidateSet("Any CPU", "x86", "x64", "NetCoreOnly", "iPhone")]
[string]$Platform = "Any CPU",
[ValidateSet("Release", "Debug")]
[string]$Configuration = "Release",
[ValidateSet("Quiet", "Minimal", "Normal", "Verbose", "Diagnostic")]
[string]$Verbosity = "Verbose",
[switch]$Experimental,
[Alias("DryRun","Noop")]
[switch]$WhatIf,
[switch]$Mono,
[switch]$SkipToolPackageRestore,
[Parameter(Position=0,Mandatory=$false,ValueFromRemainingArguments=$true)] [Parameter(Position=0,Mandatory=$false,ValueFromRemainingArguments=$true)]
[string[]]$ScriptArgs [string[]]$BuildArguments
) )
[Reflection.Assembly]::LoadWithPartialName("System.Security") | Out-Null Write-Output "Windows PowerShell $($Host.Version)"
function MD5HashFile([string] $filePath)
{
if ([string]::IsNullOrEmpty($filePath) -or !(Test-Path $filePath -PathType Leaf))
{
return $null
}
[System.IO.Stream] $file = $null;
[System.Security.Cryptography.MD5] $md5 = $null;
try
{
$md5 = [System.Security.Cryptography.MD5]::Create()
$file = [System.IO.File]::OpenRead($filePath)
return [System.BitConverter]::ToString($md5.ComputeHash($file))
}
finally
{
if ($file -ne $null)
{
$file.Dispose()
}
}
}
Write-Host "Preparing to run build script..."
if(!$PSScriptRoot){
$PSScriptRoot = Split-Path $MyInvocation.MyCommand.Path -Parent
}
$TOOLS_DIR = Join-Path $PSScriptRoot "tools" Set-StrictMode -Version 2.0; $ErrorActionPreference = "Stop"; $ConfirmPreference = "None"; trap { exit 1 }
$NUGET_EXE = Join-Path $TOOLS_DIR "nuget.exe" $PSScriptRoot = Split-Path $MyInvocation.MyCommand.Path -Parent
$CAKE_EXE = Join-Path $TOOLS_DIR "Cake/Cake.exe"
$NUGET_URL = "https://dist.nuget.org/win-x86-commandline/latest/nuget.exe"
$PACKAGES_CONFIG = Join-Path $TOOLS_DIR "packages.config"
$PACKAGES_CONFIG_MD5 = Join-Path $TOOLS_DIR "packages.config.md5sum"
# Should we use mono? ###########################################################################
$UseMono = ""; # CONFIGURATION
if($Mono.IsPresent) { ###########################################################################
Write-Verbose -Message "Using the Mono based scripting engine."
$UseMono = "-mono"
}
# Should we use the new Roslyn? $BuildProjectFile = "$PSScriptRoot\nukebuild\_build.csproj"
$UseExperimental = ""; $TempDirectory = "$PSScriptRoot\\.tmp"
if($Experimental.IsPresent -and !($Mono.IsPresent)) {
Write-Verbose -Message "Using experimental version of Roslyn."
$UseExperimental = "-experimental"
}
# Is this a dry run? $DotNetGlobalFile = "$PSScriptRoot\\global.json"
$UseDryRun = ""; $DotNetInstallUrl = "https://raw.githubusercontent.com/dotnet/cli/master/scripts/obtain/dotnet-install.ps1"
if($WhatIf.IsPresent) { $DotNetChannel = "Current"
$UseDryRun = "-dryrun"
}
# Is this a dry run? $env:DOTNET_SKIP_FIRST_TIME_EXPERIENCE = 1
$UseSkipTests = ""; $env:DOTNET_CLI_TELEMETRY_OPTOUT = 1
if($SkipTests.IsPresent) { $env:NUGET_XMLDOC_MODE = "skip"
$UseSkipTests = "-skip-tests"
}
# Make sure tools folder exists ###########################################################################
if ((Test-Path $PSScriptRoot) -and !(Test-Path $TOOLS_DIR)) { # EXECUTION
Write-Verbose -Message "Creating tools directory..." ###########################################################################
New-Item -Path $TOOLS_DIR -Type directory | out-null
}
# Make sure that packages.config exist. function ExecSafe([scriptblock] $cmd) {
if (!(Test-Path $PACKAGES_CONFIG)) { & $cmd
Write-Verbose -Message "Downloading packages.config..." if ($LASTEXITCODE) { exit $LASTEXITCODE }
try { (New-Object System.Net.WebClient).DownloadFile("http://cakebuild.net/download/bootstrapper/packages", $PACKAGES_CONFIG) } catch {
Throw "Could not download packages.config."
}
} }
# Try find NuGet.exe in path if not exists # If global.json exists, load expected version
if (!(Test-Path $NUGET_EXE)) { if (Test-Path $DotNetGlobalFile) {
Write-Verbose -Message "Trying to find nuget.exe in PATH..." $DotNetGlobal = $(Get-Content $DotNetGlobalFile | Out-String | ConvertFrom-Json)
$existingPaths = $Env:Path -Split ';' | Where-Object { (![string]::IsNullOrEmpty($_)) -and (Test-Path $_) } if ($DotNetGlobal.PSObject.Properties["sdk"] -and $DotNetGlobal.sdk.PSObject.Properties["version"]) {
$NUGET_EXE_IN_PATH = Get-ChildItem -Path $existingPaths -Filter "nuget.exe" | Select -First 1 $DotNetVersion = $DotNetGlobal.sdk.version
if ($NUGET_EXE_IN_PATH -ne $null -and (Test-Path $NUGET_EXE_IN_PATH.FullName)) {
Write-Verbose -Message "Found in PATH at $($NUGET_EXE_IN_PATH.FullName)."
$NUGET_EXE = $NUGET_EXE_IN_PATH.FullName
} }
} }
# Try download NuGet.exe if not exists # If dotnet is installed locally, and expected version is not set or installation matches the expected version
if (!(Test-Path $NUGET_EXE)) { if ((Get-Command "dotnet" -ErrorAction SilentlyContinue) -ne $null -and `
Write-Verbose -Message "Downloading NuGet.exe..." (!(Test-Path variable:DotNetVersion) -or $(& dotnet --version) -eq $DotNetVersion)) {
try { $env:DOTNET_EXE = (Get-Command "dotnet").Path
(New-Object System.Net.WebClient).DownloadFile($NUGET_URL, $NUGET_EXE)
} catch {
Throw "Could not download NuGet.exe."
}
} }
else {
# Save nuget.exe path to environment to be available to child processed $DotNetDirectory = "$TempDirectory\dotnet-win"
$ENV:NUGET_EXE = $NUGET_EXE $env:DOTNET_EXE = "$DotNetDirectory\dotnet.exe"
# Restore tools from NuGet? # Download install script
if(-Not $SkipToolPackageRestore.IsPresent) { $DotNetInstallFile = "$TempDirectory\dotnet-install.ps1"
Push-Location md -force $TempDirectory > $null
Set-Location $TOOLS_DIR (New-Object System.Net.WebClient).DownloadFile($DotNetInstallUrl, $DotNetInstallFile)
# Check for changes in packages.config and remove installed tools if true. # Install by channel or version
[string] $md5Hash = MD5HashFile($PACKAGES_CONFIG) if (!(Test-Path variable:DotNetVersion)) {
if((!(Test-Path $PACKAGES_CONFIG_MD5)) -Or ExecSafe { & $DotNetInstallFile -InstallDir $DotNetDirectory -Channel $DotNetChannel -NoPath }
($md5Hash -ne (Get-Content $PACKAGES_CONFIG_MD5 ))) { } else {
Write-Verbose -Message "Missing or changed package.config hash..." ExecSafe { & $DotNetInstallFile -InstallDir $DotNetDirectory -Version $DotNetVersion -NoPath }
Remove-Item * -Recurse -Exclude packages.config,nuget.exe
} }
Write-Verbose -Message "Restoring tools from NuGet..."
$NuGetOutput = Invoke-Expression "&`"$NUGET_EXE`" install -ExcludeVersion -OutputDirectory `"$TOOLS_DIR`""
if ($LASTEXITCODE -ne 0) {
Throw "An error occured while restoring NuGet tools."
}
else
{
$md5Hash | Out-File $PACKAGES_CONFIG_MD5 -Encoding "ASCII"
}
Write-Verbose -Message ($NuGetOutput | out-string)
Pop-Location
} }
# Make sure that Cake has been installed. Write-Output "Microsoft (R) .NET Core SDK version $(& $env:DOTNET_EXE --version)"
if (!(Test-Path $CAKE_EXE)) {
Throw "Could not find Cake.exe at $CAKE_EXE"
}
# Start Cake ExecSafe { & $env:DOTNET_EXE run --project $BuildProjectFile -- $BuildArguments }
Write-Host "Running build script..."
Invoke-Expression "& `"$CAKE_EXE`" `"$Script`" -target=`"$Target`" -platform=`"$Platform`" -configuration=`"$Configuration`" -verbosity=`"$Verbosity`" $UseSkipTests $UseMono $UseDryRun $UseExperimental $ScriptArgs"
exit $LASTEXITCODE

139
build.sh

@ -1,105 +1,72 @@
#!/usr/bin/env bash #!/usr/bin/env bash
########################################################################## echo $(bash --version 2>&1 | head -n 1)
# This is the Cake bootstrapper script for Linux and OS X.
# This file was downloaded from https://github.com/cake-build/resources
# Feel free to change this file to fit your needs.
##########################################################################
# Define directories. #CUSTOMPARAM=0
SCRIPT_DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd ) BUILD_ARGUMENTS=()
TOOLS_DIR=$SCRIPT_DIR/tools
NUGET_EXE=$TOOLS_DIR/nuget.exe
CAKE_EXE=$TOOLS_DIR/Cake/Cake.exe
PACKAGES_CONFIG=$TOOLS_DIR/packages.config
PACKAGES_CONFIG_MD5=$TOOLS_DIR/packages.config.md5sum
# Define md5sum or md5 depending on Linux/OSX
MD5_EXE=
if [[ "$(uname -s)" == "Darwin" ]]; then
MD5_EXE="md5 -r"
else
MD5_EXE="md5sum"
fi
# Define default arguments.
SCRIPT="build.cake"
TARGET="Default"
CONFIGURATION="Release"
PLATFORM="Any CPU"
VERBOSITY="verbose"
DRYRUN=
SKIP_TESTS=
SHOW_VERSION=false
SCRIPT_ARGUMENTS=()
# Parse arguments.
for i in "$@"; do for i in "$@"; do
case $1 in case $(echo $1 | awk '{print tolower($0)}') in
-s|--script) SCRIPT="$2"; shift ;; # -custom-param) CUSTOMPARAM=1;;
-t|--target) TARGET="$2"; shift ;; *) BUILD_ARGUMENTS+=("$1") ;;
-p|--platform) PLATFORM="$2"; shift ;;
-c|--configuration) CONFIGURATION="$2"; shift ;;
--skip-tests) SKIP_TESTS="-skip-tests"; shift ;;
-v|--verbosity) VERBOSITY="$2"; shift ;;
-d|--dryrun) DRYRUN="-dryrun" ;;
--version) SHOW_VERSION=true ;;
--) shift; SCRIPT_ARGUMENTS+=("$@"); break ;;
*) SCRIPT_ARGUMENTS+=("$1") ;;
esac esac
shift shift
done done
# Make sure the tools folder exist. set -eo pipefail
if [ ! -d "$TOOLS_DIR" ]; then SCRIPT_DIR=$(cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd)
mkdir "$TOOLS_DIR"
fi
# Make sure that packages.config exist. ###########################################################################
if [ ! -f "$TOOLS_DIR/packages.config" ]; then # CONFIGURATION
echo "Downloading packages.config..." ###########################################################################
curl -Lsfo "$TOOLS_DIR/packages.config" http://cakebuild.net/download/bootstrapper/packages
if [ $? -ne 0 ]; then
echo "An error occured while downloading packages.config."
exit 1
fi
fi
# Download NuGet if it does not exist. BUILD_PROJECT_FILE="$SCRIPT_DIR/nukebuild/_build.csproj"
if [ ! -f "$NUGET_EXE" ]; then TEMP_DIRECTORY="$SCRIPT_DIR//.tmp"
echo "Downloading NuGet..."
curl -Lsfo "$NUGET_EXE" https://dist.nuget.org/win-x86-commandline/latest/nuget.exe
if [ $? -ne 0 ]; then
echo "An error occured while downloading nuget.exe."
exit 1
fi
fi
# Restore tools from NuGet. DOTNET_GLOBAL_FILE="$SCRIPT_DIR//global.json"
pushd "$TOOLS_DIR" >/dev/null DOTNET_INSTALL_URL="https://raw.githubusercontent.com/dotnet/cli/master/scripts/obtain/dotnet-install.sh"
if [ ! -f $PACKAGES_CONFIG_MD5 ] || [ "$( cat $PACKAGES_CONFIG_MD5 | sed 's/\r$//' )" != "$( $MD5_EXE $PACKAGES_CONFIG | awk '{ print $1 }' )" ]; then DOTNET_CHANNEL="Current"
find . -type d ! -name . | xargs rm -rf
fi
mono "$NUGET_EXE" install -ExcludeVersion export DOTNET_CLI_TELEMETRY_OPTOUT=1
if [ $? -ne 0 ]; then export DOTNET_SKIP_FIRST_TIME_EXPERIENCE=1
echo "Could not restore NuGet packages." export NUGET_XMLDOC_MODE="skip"
exit 1
fi
$MD5_EXE $PACKAGES_CONFIG | awk '{ print $1 }' >| $PACKAGES_CONFIG_MD5 ###########################################################################
# EXECUTION
###########################################################################
popd >/dev/null function FirstJsonValue {
perl -nle 'print $1 if m{"'$1'": "([^"\-]+)",?}' <<< ${@:2}
}
# Make sure that Cake has been installed. # If global.json exists, load expected version
if [ ! -f "$CAKE_EXE" ]; then if [ -f "$DOTNET_GLOBAL_FILE" ]; then
echo "Could not find Cake.exe at '$CAKE_EXE'." DOTNET_VERSION=$(FirstJsonValue "version" $(cat "$DOTNET_GLOBAL_FILE"))
exit 1 if [ "$DOTNET_VERSION" == "" ]; then
unset DOTNET_VERSION
fi
fi fi
# Start Cake # If dotnet is installed locally, and expected version is not set or installation matches the expected version
if $SHOW_VERSION; then if [[ -x "$(command -v dotnet)" && (-z ${DOTNET_VERSION+x} || $(dotnet --version) == "$DOTNET_VERSION") ]]; then
exec mono "$CAKE_EXE" -version export DOTNET_EXE="$(command -v dotnet)"
else else
exec mono "$CAKE_EXE" $SCRIPT -verbosity=$VERBOSITY -platform="$PLATFORM" -configuration="$CONFIGURATION" -target=$TARGET $DRYRUN $SKIP_TESTS "${SCRIPT_ARGUMENTS[@]}" DOTNET_DIRECTORY="$TEMP_DIRECTORY/dotnet-unix"
export DOTNET_EXE="$DOTNET_DIRECTORY/dotnet"
# Download install script
DOTNET_INSTALL_FILE="$TEMP_DIRECTORY/dotnet-install.sh"
mkdir -p "$TEMP_DIRECTORY"
curl -Lsfo "$DOTNET_INSTALL_FILE" "$DOTNET_INSTALL_URL"
chmod +x "$DOTNET_INSTALL_FILE"
# Install by channel or version
if [ -z ${DOTNET_VERSION+x} ]; then
"$DOTNET_INSTALL_FILE" --install-dir "$DOTNET_DIRECTORY" --channel "$DOTNET_CHANNEL" --no-path
else
"$DOTNET_INSTALL_FILE" --install-dir "$DOTNET_DIRECTORY" --version "$DOTNET_VERSION" --no-path
fi
fi fi
echo "Microsoft (R) .NET Core SDK version $("$DOTNET_EXE" --version)"
"$DOTNET_EXE" run --project "$BUILD_PROJECT_FILE" -- ${BUILD_ARGUMENTS[@]}

15
cake.config

@ -1,15 +0,0 @@
; This is the default configuration file for Cake.
; This file was downloaded from https://github.com/cake-build/resources
[Nuget]
Source=https://api.nuget.org/v3/index.json
UseInProcessClient=true
LoadDependencies=false
[Paths]
Tools=./tools
Addins=./tools/Addins
Modules=./tools/Modules
[Settings]
SkipVerification=false

231
nukebuild/Build.cs

@ -0,0 +1,231 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Threading;
using Nuke.Common;
using Nuke.Common.Git;
using Nuke.Common.ProjectModel;
using Nuke.Common.Tooling;
using Nuke.Common.Tools.DotNet;
using Nuke.Common.Tools.MSBuild;
using Nuke.Common.Utilities;
using static Nuke.Common.EnvironmentInfo;
using static Nuke.Common.IO.FileSystemTasks;
using static Nuke.Common.IO.PathConstruction;
using static Nuke.Common.Tools.MSBuild.MSBuildTasks;
using static Nuke.Common.Tools.DotNet.DotNetTasks;
using static Nuke.Common.Tools.Xunit.XunitTasks;
/*
Before editing this file, install support plugin for your IDE,
running and debugging a particular target (optionally without deps) would be way easier
ReSharper/Rider - https://plugins.jetbrains.com/plugin/10803-nuke-support
VSCode - https://marketplace.visualstudio.com/items?itemName=nuke.support
*/
partial class Build : NukeBuild
{
BuildParameters Parameters { get; set; }
protected override void OnBuildInitialized()
{
Parameters = new BuildParameters(this);
Information("Building version {0} of Avalonia ({1}) using version {2} of Nuke.",
Parameters.Version,
Parameters.Configuration,
typeof(NukeBuild).Assembly.GetName().Version.ToString());
if (Parameters.IsLocalBuild)
{
Information("Repository Name: " + Parameters.RepositoryName);
Information("Repository Branch: " + Parameters.RepositoryBranch);
}
Information("Configuration: " + Parameters.Configuration);
Information("IsLocalBuild: " + Parameters.IsLocalBuild);
Information("IsRunningOnUnix: " + Parameters.IsRunningOnUnix);
Information("IsRunningOnWindows: " + Parameters.IsRunningOnWindows);
Information("IsRunningOnAzure:" + Parameters.IsRunningOnAzure);
Information("IsPullRequest: " + Parameters.IsPullRequest);
Information("IsMainRepo: " + Parameters.IsMainRepo);
Information("IsMasterBranch: " + Parameters.IsMasterBranch);
Information("IsReleaseBranch: " + Parameters.IsReleaseBranch);
Information("IsReleasable: " + Parameters.IsReleasable);
Information("IsMyGetRelease: " + Parameters.IsMyGetRelease);
Information("IsNuGetRelease: " + Parameters.IsNuGetRelease);
}
Target Clean => _ => _.Executes(() =>
{
DeleteDirectories(Parameters.BuildDirs);
EnsureCleanDirectories(Parameters.BuildDirs);
EnsureCleanDirectory(Parameters.ArtifactsDir);
EnsureCleanDirectory(Parameters.NugetRoot);
EnsureCleanDirectory(Parameters.ZipRoot);
EnsureCleanDirectory(Parameters.TestResultsRoot);
});
Target Compile => _ => _
.DependsOn(Clean)
.Executes(() =>
{
if (Parameters.IsRunningOnWindows)
MSBuild(Parameters.MSBuildSolution, c => c
.SetArgumentConfigurator(a => a.Add("/r"))
.SetConfiguration(Parameters.Configuration)
.SetVerbosity(MSBuildVerbosity.Minimal)
.AddProperty("PackageVersion", Parameters.Version)
.AddProperty("iOSRoslynPathHackRequired", "true")
.SetToolsVersion(MSBuildToolsVersion._15_0)
.AddTargets("Build")
);
else
DotNetBuild(Parameters.MSBuildSolution, c => c
.AddProperty("PackageVersion", Parameters.Version)
.SetConfiguration(Parameters.Configuration)
);
});
void RunCoreTest(string project, bool coreOnly = false)
{
if(!project.EndsWith(".csproj"))
project = System.IO.Path.Combine(project, System.IO.Path.GetFileName(project)+".csproj");
Information("Running tests from " + project);
var frameworks = new List<string>(){"netcoreapp2.0"};
foreach(var fw in frameworks)
{
if(!fw.StartsWith("netcoreapp") && coreOnly)
continue;
Information("Running for " + fw);
DotNetTest(c =>
{
c = c
.SetProjectFile(project)
.SetConfiguration(Parameters.Configuration)
.SetFramework(fw)
.EnableNoBuild()
.EnableNoRestore();
// NOTE: I can see that we could maybe add another extension method "Switch" or "If" to make this more convenient
if (Parameters.PublishTestResults)
c = c.SetLogger("trx").SetResultsDirectory(Parameters.TestResultsRoot);
return c;
});
}
}
Target RunCoreLibsTests => _ => _
.OnlyWhen(() => !Parameters.SkipTests)
.DependsOn(Compile)
.Executes(() =>
{
RunCoreTest("./tests/Avalonia.Base.UnitTests", false);
RunCoreTest("./tests/Avalonia.Controls.UnitTests", false);
RunCoreTest("./tests/Avalonia.Input.UnitTests", false);
RunCoreTest("./tests/Avalonia.Interactivity.UnitTests", false);
RunCoreTest("./tests/Avalonia.Layout.UnitTests", false);
RunCoreTest("./tests/Avalonia.Markup.UnitTests", false);
RunCoreTest("./tests/Avalonia.Markup.Xaml.UnitTests", false);
RunCoreTest("./tests/Avalonia.Styling.UnitTests", false);
RunCoreTest("./tests/Avalonia.Visuals.UnitTests", false);
RunCoreTest("./tests/Avalonia.Skia.UnitTests", false);
RunCoreTest("./tests/Avalonia.ReactiveUI.UnitTests", false);
});
Target RunRenderTests => _ => _
.OnlyWhen(() => !Parameters.SkipTests && Parameters.IsRunningOnWindows)
.DependsOn(Compile)
.Executes(() =>
{
RunCoreTest("./tests/Avalonia.Skia.RenderTests/Avalonia.Skia.RenderTests.csproj", true);
RunCoreTest("./tests/Avalonia.Direct2D1.RenderTests/Avalonia.Direct2D1.RenderTests.csproj", true);
});
Target RunDesignerTests => _ => _
.OnlyWhen(() => !Parameters.SkipTests && Parameters.IsRunningOnWindows)
.DependsOn(Compile)
.Executes(() =>
{
RunCoreTest("./tests/Avalonia.DesignerSupport.Tests", false);
});
[PackageExecutable("JetBrains.dotMemoryUnit", "dotMemoryUnit.exe")] readonly Tool DotMemoryUnit;
Target RunLeakTests => _ => _
.OnlyWhen(() => !Parameters.SkipTests && Parameters.IsRunningOnWindows)
.DependsOn(Compile)
.Executes(() =>
{
var testAssembly = "tests\\Avalonia.LeakTests\\bin\\Release\\net461\\Avalonia.LeakTests.dll";
DotMemoryUnit(
$"{XunitPath.DoubleQuoteIfNeeded()} --propagate-exit-code -- {testAssembly}",
timeout: 120_000);
});
Target ZipFiles => _ => _
.After(CreateNugetPackages, Compile, RunCoreLibsTests, Package)
.Executes(() =>
{
var data = Parameters;
Zip(data.ZipCoreArtifacts, data.BinRoot);
Zip(data.ZipNuGetArtifacts, data.NugetRoot);
Zip(data.ZipTargetControlCatalogDesktopDir,
GlobFiles(data.ZipSourceControlCatalogDesktopDir, "*.dll").Concat(
GlobFiles(data.ZipSourceControlCatalogDesktopDir, "*.config")).Concat(
GlobFiles(data.ZipSourceControlCatalogDesktopDir, "*.so")).Concat(
GlobFiles(data.ZipSourceControlCatalogDesktopDir, "*.dylib")).Concat(
GlobFiles(data.ZipSourceControlCatalogDesktopDir, "*.exe")));
});
Target CreateNugetPackages => _ => _
.DependsOn(Compile)
.After(RunTests)
.Executes(() =>
{
if (Parameters.IsRunningOnWindows)
MSBuild(Parameters.MSBuildSolution, c => c
.SetConfiguration(Parameters.Configuration)
.SetVerbosity(MSBuildVerbosity.Minimal)
.AddProperty("PackageVersion", Parameters.Version)
.AddProperty("iOSRoslynPathHackRequired", "true")
.SetToolsVersion(MSBuildToolsVersion._15_0)
.AddTargets("Pack"));
else
DotNetPack(Parameters.MSBuildSolution, c =>
c.SetConfiguration(Parameters.Configuration)
.AddProperty("PackageVersion", Parameters.Version));
});
Target RunTests => _ => _
.DependsOn(RunCoreLibsTests)
.DependsOn(RunRenderTests)
.DependsOn(RunDesignerTests)
.DependsOn(RunLeakTests);
Target Package => _ => _
.DependsOn(RunTests)
.DependsOn(CreateNugetPackages);
Target CiAzureLinux => _ => _
.DependsOn(RunTests);
Target CiAzureOSX => _ => _
.DependsOn(Package)
.DependsOn(ZipFiles);
Target CiAzureWindows => _ => _
.DependsOn(Package)
.DependsOn(ZipFiles);
public static int Main() =>
RuntimeInformation.IsOSPlatform(OSPlatform.Windows)
? Execute<Build>(x => x.Package)
: Execute<Build>(x => x.RunTests);
}

140
nukebuild/BuildParameters.cs

@ -0,0 +1,140 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Xml.Linq;
using Nuke.Common;
using Nuke.Common.BuildServers;
using Nuke.Common.Execution;
using Nuke.Common.IO;
using static Nuke.Common.IO.FileSystemTasks;
using static Nuke.Common.IO.PathConstruction;
using static Nuke.Common.Tools.MSBuild.MSBuildTasks;
public partial class Build
{
[Parameter("configuration")]
public string NukeArgConfiguration { get; set; }
[Parameter("skip-tests")]
public bool NukeArgSkipTests { get; set; }
[Parameter("force-nuget-version")]
public string NukeArgForceNugetVersion { get; set; }
public class BuildParameters
{
public string Configuration { get; }
public bool SkipTests { get; }
public string MainRepo { get; }
public string MasterBranch { get; }
public string RepositoryName { get; }
public string RepositoryBranch { get; }
public string ReleaseConfiguration { get; }
public string ReleaseBranchPrefix { get; }
public string MSBuildSolution { get; }
public bool IsLocalBuild { get; }
public bool IsRunningOnUnix { get; }
public bool IsRunningOnWindows { get; }
public bool IsRunningOnAzure { get; }
public bool IsPullRequest { get; }
public bool IsMainRepo { get; }
public bool IsMasterBranch { get; }
public bool IsReleaseBranch { get; }
public bool IsReleasable { get; }
public bool IsMyGetRelease { get; }
public bool IsNuGetRelease { get; }
public bool PublishTestResults { get; }
public string Version { get; }
public AbsolutePath ArtifactsDir { get; }
public AbsolutePath NugetRoot { get; }
public AbsolutePath ZipRoot { get; }
public AbsolutePath BinRoot { get; }
public AbsolutePath TestResultsRoot { get; }
public string DirSuffix { get; }
public List<string> BuildDirs { get; }
public string FileZipSuffix { get; }
public AbsolutePath ZipCoreArtifacts { get; }
public AbsolutePath ZipNuGetArtifacts { get; }
public AbsolutePath ZipSourceControlCatalogDesktopDir { get; }
public AbsolutePath ZipTargetControlCatalogDesktopDir { get; }
public BuildParameters(Build b)
{
// ARGUMENTS
Configuration = b.NukeArgConfiguration ?? "Release";
SkipTests = b.NukeArgSkipTests;
// CONFIGURATION
MainRepo = "https://github.com/AvaloniaUI/Avalonia";
MasterBranch = "refs/heads/master";
ReleaseBranchPrefix = "refs/heads/release/";
ReleaseConfiguration = "Release";
MSBuildSolution = RootDirectory / "dirs.proj";
// PARAMETERS
IsLocalBuild = Host == HostType.Console;
IsRunningOnUnix = Environment.OSVersion.Platform == PlatformID.Unix ||
Environment.OSVersion.Platform == PlatformID.MacOSX;
IsRunningOnWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
IsRunningOnAzure = Host == HostType.TeamServices ||
Environment.GetEnvironmentVariable("LOGNAME") == "vsts";
if (IsRunningOnAzure)
{
RepositoryName = TeamServices.Instance.RepositoryUri;
RepositoryBranch = TeamServices.Instance.SourceBranch;
IsPullRequest = TeamServices.Instance.PullRequestId.HasValue;
IsMainRepo = StringComparer.OrdinalIgnoreCase.Equals(MainRepo, TeamServices.Instance.RepositoryUri);
}
IsMainRepo =
StringComparer.OrdinalIgnoreCase.Equals(MainRepo,
RepositoryName);
IsMasterBranch = StringComparer.OrdinalIgnoreCase.Equals(MasterBranch,
RepositoryBranch);
IsReleaseBranch = RepositoryBranch?.StartsWith(ReleaseBranchPrefix, StringComparison.OrdinalIgnoreCase) ==
true;
IsReleasable = StringComparer.OrdinalIgnoreCase.Equals(ReleaseConfiguration, Configuration);
IsMyGetRelease = IsReleasable;
IsNuGetRelease = IsMainRepo && IsReleasable && IsReleaseBranch;
// VERSION
Version = b.NukeArgForceNugetVersion ?? GetVersion();
if (IsRunningOnAzure)
{
if (!IsNuGetRelease)
{
// Use AssemblyVersion with Build as version
Version += "-build" + Environment.GetEnvironmentVariable("BUILD_BUILDID") + "-beta";
}
PublishTestResults = true;
}
// DIRECTORIES
ArtifactsDir = RootDirectory / "artifacts";
NugetRoot = ArtifactsDir / "nuget";
ZipRoot = ArtifactsDir / "zip";
BinRoot = ArtifactsDir / "bin";
TestResultsRoot = ArtifactsDir / "test-results";
BuildDirs = GlobDirectories(RootDirectory, "**bin").Concat(GlobDirectories(RootDirectory, "**obj")).ToList();
DirSuffix = Configuration;
FileZipSuffix = Version + ".zip";
ZipCoreArtifacts = ZipRoot / ("Avalonia-" + FileZipSuffix);
ZipNuGetArtifacts = ZipRoot / ("Avalonia-NuGet-" + FileZipSuffix);
ZipSourceControlCatalogDesktopDir =
RootDirectory / ("samples/ControlCatalog.Desktop/bin/" + DirSuffix + "/net461");
ZipTargetControlCatalogDesktopDir = ZipRoot / ("ControlCatalog.Desktop-" + FileZipSuffix);
}
private static string GetVersion()
{
var xdoc = XDocument.Load("./build/SharedVersion.props");
return xdoc.Descendants().First(x => x.Name.LocalName == "Version").Value;
}
}
}

79
nukebuild/Shims.cs

@ -0,0 +1,79 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.IO.Compression;
using System.Linq;
using Nuke.Common;
using Nuke.Common.IO;
public partial class Build
{
static void Information(string info)
{
Logger.Info(info);
}
static void Information(string info, params object[] args)
{
Logger.Info(info, args);
}
private void Zip(PathConstruction.AbsolutePath target, params string[] paths) => Zip(target, paths.AsEnumerable());
private void Zip(PathConstruction.AbsolutePath target, IEnumerable<string> paths)
{
var targetPath = target.ToString();
bool finished = false, atLeastOneFileAdded = false;
try
{
using (var targetStream = File.Create(targetPath))
using(var archive = new System.IO.Compression.ZipArchive(targetStream, ZipArchiveMode.Create))
{
void AddFile(string path, string relativePath)
{
var e = archive.CreateEntry(relativePath.Replace("\\", "/"), CompressionLevel.Optimal);
using (var entryStream = e.Open())
using (var fileStream = File.OpenRead(path))
fileStream.CopyTo(entryStream);
atLeastOneFileAdded = true;
}
foreach (var path in paths)
{
if (Directory.Exists(path))
{
var dirInfo = new DirectoryInfo(path);
var rootPath = Path.GetDirectoryName(dirInfo.FullName);
foreach(var fsEntry in dirInfo.EnumerateFileSystemInfos("*", SearchOption.AllDirectories))
{
if (fsEntry is FileInfo)
{
var relPath = Path.GetRelativePath(rootPath, fsEntry.FullName);
AddFile(fsEntry.FullName, relPath);
}
}
}
else if(File.Exists(path))
{
var name = Path.GetFileName(path);
AddFile(path, name);
}
}
}
finished = true;
}
finally
{
try
{
if (!finished || !atLeastOneFileAdded)
File.Delete(targetPath);
}
catch
{
//Ignore
}
}
}
}

35
nukebuild/_build.csproj

@ -0,0 +1,35 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp2.0</TargetFramework>
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
<RootNamespace></RootNamespace>
<IsPackable>False</IsPackable>
<NoWarn>CS0649;CS0169</NoWarn>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Nuke.Common" Version="0.12.3" />
<PackageReference Include="xunit.runner.console" Version="2.3.1" />
<PackageReference Include="JetBrains.dotMemoryUnit" Version="3.0.20171219.105559" />
</ItemGroup>
<ItemGroup>
<NukeMetadata Include="**\*.json" Exclude="bin\**;obj\**" />
<NukeExternalFiles Include="**\*.*.ext" Exclude="bin\**;obj\**" />
<None Remove="*.csproj.DotSettings;*.ref.*.txt" />
<!-- Common build related files -->
<None Include="..\build.ps1" />
<None Include="..\build.sh" />
<None Include="..\.nuke" />
<None Include="..\global.json" Condition="Exists('..\global.json')" />
<None Include="..\nuget.config" Condition="Exists('..\nuget.config')" />
<None Include="..\Jenkinsfile" Condition="Exists('..\Jenkinsfile')" />
<None Include="..\appveyor.yml" Condition="Exists('..\appveyor.yml')" />
<None Include="..\.travis.yml" Condition="Exists('..\.travis.yml')" />
<None Include="..\GitVersion.yml" Condition="Exists('..\GitVersion.yml')" />
</ItemGroup>
</Project>

24
nukebuild/_build.csproj.DotSettings

@ -0,0 +1,24 @@
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<s:Boolean x:Key="/Default/CodeInspection/ImplicitNullability/EnableFields/@EntryValue">False</s:Boolean>
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpCodeStyle/DEFAULT_INTERNAL_MODIFIER/@EntryValue">Implicit</s:String>
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpCodeStyle/DEFAULT_PRIVATE_MODIFIER/@EntryValue">Implicit</s:String>
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpCodeStyle/METHOD_OR_OPERATOR_BODY/@EntryValue">ExpressionBody</s:String>
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpCodeStyle/ThisQualifier/INSTANCE_MEMBERS_QUALIFY_MEMBERS/@EntryValue">0</s:String>
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/ANONYMOUS_METHOD_DECLARATION_BRACES/@EntryValue">NEXT_LINE</s:String>
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/KEEP_USER_LINEBREAKS/@EntryValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/WRAP_AFTER_INVOCATION_LPAR/@EntryValue">False</s:Boolean>
<s:Int64 x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/MAX_ATTRIBUTE_LENGTH_FOR_SAME_LINE/@EntryValue">120</s:Int64>
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/PLACE_FIELD_ATTRIBUTE_ON_SAME_LINE_EX/@EntryValue">IF_OWNER_IS_SINGLE_LINE</s:String>
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/WRAP_ARGUMENTS_STYLE/@EntryValue">WRAP_IF_LONG</s:String>
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/PLACE_SIMPLE_ANONYMOUSMETHOD_ON_SINGLE_LINE/@EntryValue">False</s:Boolean>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=PrivateInstanceFields/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=PrivateStaticFields/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /&gt;</s:String>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpAttributeForSingleLineMethodUpgrade/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpKeepExistingMigration/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpPlaceEmbeddedOnSameLineMigration/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpRenamePlacementToArrangementMigration/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpUseContinuousIndentInsideBracesMigration/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EAddAccessorOwnerDeclarationBracesMigration/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002ECSharpPlaceAttributeOnSameLineMigration/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EMigrateBlankLinesAroundFieldToBlankLinesAroundProperty/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EMigrateThisQualifierSettings/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>

6
readme.md

@ -2,9 +2,9 @@
# Avalonia # Avalonia
| Gitter Chat | Build Status (Win, Linux, OSX) | Appveyor Build Status | Open Collective | | Gitter Chat | Build Status (Win, Linux, OSX) | Open Collective |
|---|---|---|---| |---|---|---|
| [![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/AvaloniaUI/Avalonia?utm_campaign=pr-badge&utm_content=badge&utm_medium=badge&utm_source=badge) | [![Build Status](https://dev.azure.com/AvaloniaUI/AvaloniaUI/_apis/build/status/AvaloniaUI.Avalonia)](https://dev.azure.com/AvaloniaUI/AvaloniaUI/_build/latest?definitionId=4) | [![Build status](https://ci.appveyor.com/api/projects/status/hubk3k0w9idyibfg/branch/master?svg=true)](https://ci.appveyor.com/project/AvaloniaUI/Avalonia/branch/master) | [![Backers on Open Collective](https://opencollective.com/Avalonia/backers/badge.svg)](#backers) [![Sponsors on Open Collective](https://opencollective.com/Avalonia/sponsors/badge.svg)](#sponsors) | | [![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/AvaloniaUI/Avalonia?utm_campaign=pr-badge&utm_content=badge&utm_medium=badge&utm_source=badge) | [![Build Status](https://dev.azure.com/AvaloniaUI/AvaloniaUI/_apis/build/status/AvaloniaUI.Avalonia)](https://dev.azure.com/AvaloniaUI/AvaloniaUI/_build/latest?definitionId=4) | [![Backers on Open Collective](https://opencollective.com/Avalonia/backers/badge.svg)](#backers) [![Sponsors on Open Collective](https://opencollective.com/Avalonia/sponsors/badge.svg)](#sponsors) |
## About ## About

10
samples/ControlCatalog.NetCore/Program.cs

@ -23,6 +23,7 @@ namespace ControlCatalog.NetCore
break; break;
} }
} }
if (args.Contains("--fbdev")) if (args.Contains("--fbdev"))
AppBuilder.Configure<App>().InitializeWithLinuxFramebuffer(tl => AppBuilder.Configure<App>().InitializeWithLinuxFramebuffer(tl =>
{ {
@ -30,7 +31,12 @@ namespace ControlCatalog.NetCore
System.Threading.ThreadPool.QueueUserWorkItem(_ => ConsoleSilencer()); System.Threading.ThreadPool.QueueUserWorkItem(_ => ConsoleSilencer());
}); });
else else
BuildAvaloniaApp().Start<MainWindow>(); BuildAvaloniaApp().Start(AppMain, args);
}
static void AppMain(Application app, string[] args)
{
app.Run(new MainWindow());
} }
/// <summary> /// <summary>
@ -46,4 +52,4 @@ namespace ControlCatalog.NetCore
Console.ReadKey(true); Console.ReadKey(true);
} }
} }
} }

13
samples/ControlCatalog/DecoratedWindow.xaml

@ -1,9 +1,8 @@
<Window xmlns="https://github.com/avaloniaui" MinWidth="500" MinHeight="300" <Window xmlns="https://github.com/avaloniaui" MinWidth="500" MinHeight="300"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="ControlCatalog.DecoratedWindow" x:Class="ControlCatalog.DecoratedWindow"
Title="Avalonia Control Gallery" Title="Avalonia Control Gallery"
Icon="/Assets/test_icon.ico" xmlns:local="clr-namespace:ControlCatalog" HasSystemDecorations="False" Name="Window">
xmlns:local="clr-namespace:ControlCatalog" HasSystemDecorations="False">
<Grid RowDefinitions="5,*,5" ColumnDefinitions="5,*,5"> <Grid RowDefinitions="5,*,5" ColumnDefinitions="5,*,5">
<DockPanel Grid.Column="1" Grid.Row="1" > <DockPanel Grid.Column="1" Grid.Row="1" >
<Grid Name="TitleBar" Background="LightBlue" DockPanel.Dock="Top" ColumnDefinitions="Auto,*,Auto"> <Grid Name="TitleBar" Background="LightBlue" DockPanel.Dock="Top" ColumnDefinitions="Auto,*,Auto">
@ -20,7 +19,13 @@
</StackPanel> </StackPanel>
</Grid> </Grid>
<Border Background="White" Margin="5"> <Border Background="White" Margin="5">
<TextBlock>Hello world!</TextBlock> <StackPanel>
<TextBlock>Hello world!</TextBlock>
<CheckBox IsChecked="{Binding ElementName=Window, Path=HasSystemDecorations}">Decorated</CheckBox>
<CheckBox IsChecked="{Binding ElementName=Window, Path=CanResize}">CanResize</CheckBox>
</StackPanel>
</Border> </Border>
</DockPanel> </DockPanel>
<Border Name="TopLeft" Background="Red"/> <Border Name="TopLeft" Background="Red"/>

4
samples/ControlCatalog/Pages/TextBoxPage.xaml

@ -46,8 +46,8 @@
<TextBlock Classes="h2">res fonts</TextBlock> <TextBlock Classes="h2">res fonts</TextBlock>
<TextBox Width="200" Text="Custom font regular" FontWeight="Normal" FontStyle="Normal" FontFamily="avares://ControlCatalog/Assets/Fonts#Source Sans Pro"/> <TextBox Width="200" Text="Custom font regular" FontWeight="Normal" FontStyle="Normal" FontFamily="avares://ControlCatalog/Assets/Fonts#Source Sans Pro"/>
<TextBox Width="200" Text="Custom font bold" FontWeight="Bold" FontStyle="Normal" FontFamily="avares://ControlCatalog/Assets/Fonts#Source Sans Pro"/> <TextBox Width="200" Text="Custom font bold" FontWeight="Bold" FontStyle="Normal" FontFamily="avares://ControlCatalog/Assets/Fonts#Source Sans Pro"/>
<TextBox Width="200" Text="Custom font italic" FontWeight="Normal" FontStyle="Italic" FontFamily="avares://ControlCatalog/Assets/Fonts/SourceSansPro-Italic.ttf#Source Sans Pro"/> <TextBox Width="200" Text="Custom font italic" FontWeight="Normal" FontStyle="Italic" FontFamily="/Assets/Fonts/SourceSansPro-Italic.ttf#Source Sans Pro"/>
<TextBox Width="200" Text="Custom font italic bold" FontWeight="Bold" FontStyle="Italic" FontFamily="avares://ControlCatalog/Assets/Fonts/SourceSansPro-*.ttf#Source Sans Pro"/> <TextBox Width="200" Text="Custom font italic bold" FontWeight="Bold" FontStyle="Italic" FontFamily="/Assets/Fonts/SourceSansPro-*.ttf#Source Sans Pro"/>
</StackPanel> </StackPanel>
</StackPanel> </StackPanel>
</StackPanel> </StackPanel>

9
src/Avalonia.Controls/AppBuilderBase.cs

@ -145,6 +145,15 @@ namespace Avalonia.Controls
Instance.Run(mainWindow); Instance.Run(mainWindow);
} }
public delegate void AppMainDelegate(Application app, string[] args);
public void Start(AppMainDelegate main, string[] args)
{
Setup();
BeforeStartCallback(Self);
main(Instance, args);
}
/// <summary> /// <summary>
/// Sets up the platform-specific services for the application, but does not run it. /// Sets up the platform-specific services for the application, but does not run it.
/// </summary> /// </summary>

4
src/Avalonia.Themes.Default/MenuItem.xaml

@ -49,7 +49,7 @@
ObeyScreenEdges="True"> ObeyScreenEdges="True">
<Border Background="{TemplateBinding Background}" <Border Background="{TemplateBinding Background}"
BorderBrush="{DynamicResource ThemeBorderMidBrush}" BorderBrush="{DynamicResource ThemeBorderMidBrush}"
BorderThickness="1"> BorderThickness="{TemplateBinding BorderThickness}">
<ScrollViewer> <ScrollViewer>
<Panel> <Panel>
<ItemsPresenter Name="PART_ItemsPresenter" <ItemsPresenter Name="PART_ItemsPresenter"
@ -107,7 +107,7 @@
ObeyScreenEdges="True"> ObeyScreenEdges="True">
<Border Background="{TemplateBinding Background}" <Border Background="{TemplateBinding Background}"
BorderBrush="{DynamicResource ThemeBorderMidBrush}" BorderBrush="{DynamicResource ThemeBorderMidBrush}"
BorderThickness="1"> BorderThickness="{TemplateBinding BorderThickness}">
<ScrollViewer> <ScrollViewer>
<Panel> <Panel>
<ItemsPresenter Name="PART_ItemsPresenter" <ItemsPresenter Name="PART_ItemsPresenter"

16
src/Avalonia.Visuals/Media/FontFamily.cs

@ -40,9 +40,10 @@ namespace Avalonia.Media
/// </summary> /// </summary>
/// <param name="name">The name of the <see cref="T:Avalonia.Media.FontFamily" />.</param> /// <param name="name">The name of the <see cref="T:Avalonia.Media.FontFamily" />.</param>
/// <param name="source">The source of font resources.</param> /// <param name="source">The source of font resources.</param>
public FontFamily(string name, Uri source) : this(name) /// <param name="baseUri"></param>
public FontFamily(string name, Uri source, Uri baseUri = null) : this(name)
{ {
Key = new FontFamilyKey(source); Key = new FontFamilyKey(source, baseUri);
} }
/// <summary> /// <summary>
@ -87,11 +88,12 @@ namespace Avalonia.Media
/// Parses a <see cref="T:Avalonia.Media.FontFamily"/> string. /// Parses a <see cref="T:Avalonia.Media.FontFamily"/> string.
/// </summary> /// </summary>
/// <param name="s">The <see cref="T:Avalonia.Media.FontFamily"/> string.</param> /// <param name="s">The <see cref="T:Avalonia.Media.FontFamily"/> string.</param>
/// <param name="baseUri"></param>
/// <returns></returns> /// <returns></returns>
/// <exception cref="ArgumentException"> /// <exception cref="ArgumentException">
/// Specified family is not supported. /// Specified family is not supported.
/// </exception> /// </exception>
public static FontFamily Parse(string s) public static FontFamily Parse(string s, Uri baseUri = null)
{ {
if (string.IsNullOrEmpty(s)) if (string.IsNullOrEmpty(s))
{ {
@ -112,7 +114,13 @@ namespace Avalonia.Media
case 2: case 2:
{ {
return new FontFamily(segments[1], new Uri(segments[0], UriKind.RelativeOrAbsolute)); var uri = segments[0].StartsWith("/")
? new Uri(segments[0], UriKind.Relative)
: new Uri(segments[0], UriKind.RelativeOrAbsolute);
return uri.IsAbsoluteUri
? new FontFamily(segments[1], uri)
: new FontFamily(segments[1], uri, baseUri);
} }
default: default:

63
src/Avalonia.Visuals/Media/Fonts/FontFamilyKey.cs

@ -2,7 +2,6 @@
// Licensed under the MIT license. See licence.md file in the project root for full license information. // Licensed under the MIT license. See licence.md file in the project root for full license information.
using System; using System;
using System.Linq;
namespace Avalonia.Media.Fonts namespace Avalonia.Media.Fonts
{ {
@ -12,48 +11,26 @@ namespace Avalonia.Media.Fonts
public class FontFamilyKey public class FontFamilyKey
{ {
/// <summary> /// <summary>
/// Creates a new instance of <see cref="FontFamilyKey"/> and extracts <see cref="Location"/> and <see cref="FileName"/> from given <see cref="Uri"/> /// Creates a new instance of <see cref="FontFamilyKey"/>
/// </summary> /// </summary>
/// <param name="source"></param> /// <param name="source"></param>
public FontFamilyKey(Uri source) /// <param name="baseUri"></param>
public FontFamilyKey(Uri source, Uri baseUri = null)
{ {
if (source == null) Source = source ?? throw new ArgumentNullException(nameof(source));
{
throw new ArgumentNullException(nameof(source));
}
if (source.AbsolutePath.Contains(".ttf")) BaseUri = baseUri;
{
var filePathWithoutExtension = source.AbsolutePath.Replace(".ttf", string.Empty);
var fileNameWithoutExtension = filePathWithoutExtension.Split('.').Last();
FileName = fileNameWithoutExtension + ".ttf";
Location = new Uri(source.OriginalString.Replace("." + FileName, string.Empty), UriKind.RelativeOrAbsolute);
}
else
{
if (source.AbsolutePath.Contains(".otf"))
{
var filePathWithoutExtension = source.AbsolutePath.Replace(".otf", string.Empty);
var fileNameWithoutExtension = filePathWithoutExtension.Split('.').Last();
FileName = fileNameWithoutExtension + ".otf";
Location = new Uri(source.OriginalString.Replace("." + FileName, string.Empty), UriKind.RelativeOrAbsolute);
}
else
{
Location = source;
}
}
} }
/// <summary> /// <summary>
/// Location of stored font asset that belongs to a <see cref="FontFamily"/> /// Source of stored font asset that belongs to a <see cref="FontFamily"/>
/// </summary> /// </summary>
public Uri Location { get; } public Uri Source { get; }
/// <summary> /// <summary>
/// Optional filename for a font asset that belongs to a <see cref="FontFamily"/> /// A base URI to use if <see cref="Source"/> is relative
/// </summary> /// </summary>
public string FileName { get; } public Uri BaseUri { get; }
/// <summary> /// <summary>
/// Returns a hash code for this instance. /// Returns a hash code for this instance.
@ -67,14 +44,14 @@ namespace Avalonia.Media.Fonts
{ {
var hash = (int)2166136261; var hash = (int)2166136261;
if (Location != null) if (Source != null)
{ {
hash = (hash * 16777619) ^ Location.GetHashCode(); hash = (hash * 16777619) ^ Source.GetHashCode();
} }
if (FileName != null) if (BaseUri != null)
{ {
hash = (hash * 16777619) ^ FileName.GetHashCode(); hash = (hash * 16777619) ^ BaseUri.GetHashCode();
} }
return hash; return hash;
@ -95,12 +72,12 @@ namespace Avalonia.Media.Fonts
return false; return false;
} }
if (Location != other.Location) if (Source != other.Source)
{ {
return false; return false;
} }
if (FileName != other.FileName) if (BaseUri != other.BaseUri)
{ {
return false; return false;
} }
@ -116,16 +93,12 @@ namespace Avalonia.Media.Fonts
/// </returns> /// </returns>
public override string ToString() public override string ToString()
{ {
if (FileName == null) if (!Source.IsAbsoluteUri && BaseUri != null)
{ {
return Location.PathAndQuery; return BaseUri.Authority + Source;
} }
var builder = new UriBuilder(Location); return Source.ToString();
builder.Path += "." + FileName;
return builder.ToString();
} }
} }
} }

94
src/Avalonia.Visuals/Media/Fonts/FontFamilyLoader.cs

@ -4,7 +4,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Reflection;
using Avalonia.Platform; using Avalonia.Platform;
namespace Avalonia.Media.Fonts namespace Avalonia.Media.Fonts
@ -18,23 +17,35 @@ namespace Avalonia.Media.Fonts
s_assetLoader = AvaloniaLocator.Current.GetService<IAssetLoader>(); s_assetLoader = AvaloniaLocator.Current.GetService<IAssetLoader>();
} }
/// <summary>
/// Loads all font assets that belong to the specified <see cref="FontFamilyKey"/>
/// </summary>
/// <param name="fontFamilyKey"></param>
/// <returns></returns>
public static IEnumerable<Uri> LoadFontAssets(FontFamilyKey fontFamilyKey) public static IEnumerable<Uri> LoadFontAssets(FontFamilyKey fontFamilyKey)
{ {
return fontFamilyKey.FileName != null var sourceWithoutArguments = fontFamilyKey.Source.OriginalString.Split('?').First();
? GetFontAssetsByFileName(fontFamilyKey.Location, fontFamilyKey.FileName)
: GetFontAssetsByLocation(fontFamilyKey.Location); if (sourceWithoutArguments.EndsWith(".ttf")
|| sourceWithoutArguments.EndsWith(".otf"))
{
return GetFontAssetsByExpression(fontFamilyKey);
}
return GetFontAssetsBySource(fontFamilyKey);
} }
/// <summary> /// <summary>
/// Searches for font assets at a given location and returns a quantity of found assets /// Searches for font assets at a given location and returns a quantity of found assets
/// </summary> /// </summary>
/// <param name="location"></param> /// <param name="fontFamilyKey"></param>
/// <returns></returns> /// <returns></returns>
private static IEnumerable<Uri> GetFontAssetsByLocation(Uri location) private static IEnumerable<Uri> GetFontAssetsBySource(FontFamilyKey fontFamilyKey)
{ {
var availableAssets = s_assetLoader.GetAssets(location, null); var availableAssets = s_assetLoader.GetAssets(fontFamilyKey.Source, fontFamilyKey.BaseUri);
var matchingAssets = availableAssets.Where(x => x.AbsolutePath.EndsWith(".ttf") || x.AbsolutePath.EndsWith(".otf")); var matchingAssets =
availableAssets.Where(x => x.AbsolutePath.EndsWith(".ttf") || x.AbsolutePath.EndsWith(".otf"));
return matchingAssets; return matchingAssets;
} }
@ -43,20 +54,73 @@ namespace Avalonia.Media.Fonts
/// Searches for font assets at a given location and only accepts assets that fit to a given filename expression. /// Searches for font assets at a given location and only accepts assets that fit to a given filename expression.
/// <para>File names can target multiple files with * wildcard. For example "FontFile*.ttf"</para> /// <para>File names can target multiple files with * wildcard. For example "FontFile*.ttf"</para>
/// </summary> /// </summary>
/// <param name="location"></param> /// <param name="fontFamilyKey"></param>
/// <param name="fileName"></param>
/// <returns></returns> /// <returns></returns>
private static IEnumerable<Uri> GetFontAssetsByFileName(Uri location, string fileName) private static IEnumerable<Uri> GetFontAssetsByExpression(FontFamilyKey fontFamilyKey)
{ {
var availableResources = s_assetLoader.GetAssets(location, null); var fileName = GetFileName(fontFamilyKey, out var fileExtension, out var location);
var compareTo = location.AbsolutePath + "." + fileName.Split('*').First(); var availableResources = s_assetLoader.GetAssets(location, fontFamilyKey.BaseUri);
var matchingResources = string compareTo;
availableResources.Where(x => x.AbsolutePath.Contains(compareTo) && (x.AbsolutePath.EndsWith(".ttf") || x.AbsolutePath.EndsWith(".otf")));
if (fontFamilyKey.Source.IsAbsoluteUri)
{
if (fontFamilyKey.Source.Scheme == "resm")
{
compareTo = location.AbsolutePath + "." + fileName.Split('*').First();
}
else
{
compareTo = location.AbsolutePath + fileName.Split('*').First();
}
}
else
{
compareTo = location.AbsolutePath + fileName.Split('*').First();
}
var matchingResources = availableResources.Where(
x => x.AbsolutePath.Contains(compareTo)
&& x.AbsolutePath.EndsWith(fileExtension));
return matchingResources; return matchingResources;
} }
private static string GetFileName(FontFamilyKey fontFamilyKey, out string fileExtension, out Uri location)
{
if (fontFamilyKey.Source.IsAbsoluteUri && fontFamilyKey.Source.Scheme == "resm")
{
fileExtension = "." + fontFamilyKey.Source.AbsolutePath.Split('.').LastOrDefault();
var fileName = fontFamilyKey.Source.LocalPath.Replace(fileExtension, string.Empty).Split('.').LastOrDefault();
location = new Uri(fontFamilyKey.Source.AbsoluteUri.Replace("." + fileName + fileExtension, string.Empty), UriKind.RelativeOrAbsolute);
return fileName;
}
var pathSegments = fontFamilyKey.Source.OriginalString.Split('/');
var fileNameWithExtension = pathSegments.Last();
var fileNameSegments = fileNameWithExtension.Split('.');
fileExtension = "." + fileNameSegments.Last();
if (fontFamilyKey.BaseUri != null)
{
var relativePath = fontFamilyKey.Source.OriginalString
.Replace(fileNameWithExtension, string.Empty);
location = new Uri(fontFamilyKey.BaseUri, relativePath);
}
else
{
location = new Uri(fontFamilyKey.Source.AbsolutePath.Replace(fileNameWithExtension, string.Empty));
}
return fileNameSegments.First();
}
} }
} }

9
src/Avalonia.Visuals/Rendering/ImmediateRenderer.cs

@ -266,7 +266,14 @@ namespace Avalonia.Rendering
if (clipToBounds) if (clipToBounds)
{ {
clipRect = clipRect.Intersect(new Rect(visual.Bounds.Size)); if (visual.RenderTransform != null)
{
clipRect = new Rect(visual.Bounds.Size);
}
else
{
clipRect = clipRect.Intersect(new Rect(visual.Bounds.Size));
}
} }
using (context.PushPostTransform(m)) using (context.PushPostTransform(m))

1
src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj

@ -10,6 +10,7 @@
<ItemGroup> <ItemGroup>
<Compile Include="AvaloniaXamlLoader.cs" /> <Compile Include="AvaloniaXamlLoader.cs" />
<Compile Include="Converters\AvaloniaUriTypeConverter.cs" /> <Compile Include="Converters\AvaloniaUriTypeConverter.cs" />
<Compile Include="Converters\FontFamilyTypeConverter.cs" />
<Compile Include="Converters\MemberSelectorTypeConverter.cs" /> <Compile Include="Converters\MemberSelectorTypeConverter.cs" />
<Compile Include="Converters\ParseTypeConverter.cs" /> <Compile Include="Converters\ParseTypeConverter.cs" />
<Compile Include="Converters\SetterValueTypeConverter.cs" /> <Compile Include="Converters\SetterValueTypeConverter.cs" />

9
src/Markup/Avalonia.Markup.Xaml/AvaloniaTypeConverters.cs

@ -11,6 +11,8 @@ using Avalonia.Controls.Templates;
namespace Avalonia.Markup.Xaml namespace Avalonia.Markup.Xaml
{ {
using Avalonia.Media;
/// <summary> /// <summary>
/// Maintains a repository of <see cref="TypeConverter"/>s for XAML parsing on top of those /// Maintains a repository of <see cref="TypeConverter"/>s for XAML parsing on top of those
/// maintained by <see cref="TypeDescriptor"/>. /// maintained by <see cref="TypeDescriptor"/>.
@ -37,8 +39,9 @@ namespace Avalonia.Markup.Xaml
{ typeof(Selector), typeof(SelectorTypeConverter) }, { typeof(Selector), typeof(SelectorTypeConverter) },
{ typeof(TimeSpan), typeof(TimeSpanTypeConverter) }, { typeof(TimeSpan), typeof(TimeSpanTypeConverter) },
{ typeof(WindowIcon), typeof(IconTypeConverter) }, { typeof(WindowIcon), typeof(IconTypeConverter) },
{ typeof(CultureInfo), typeof(CultureInfoConverter)}, { typeof(CultureInfo), typeof(CultureInfoConverter) },
{ typeof(Uri), typeof(AvaloniaUriTypeConverter)} { typeof(Uri), typeof(AvaloniaUriTypeConverter) },
{ typeof(FontFamily), typeof(FontFamilyTypeConverter) }
}; };
/// <summary> /// <summary>
@ -84,4 +87,4 @@ namespace Avalonia.Markup.Xaml
/// <param name="converterType">The converter type. Maybe be a non-constructed generic type.</param> /// <param name="converterType">The converter type. Maybe be a non-constructed generic type.</param>
public static void Register(Type type, Type converterType) => _converters[type] = converterType; public static void Register(Type type, Type converterType) => _converters[type] = converterType;
} }
} }

28
src/Markup/Avalonia.Markup.Xaml/Converters/FontFamilyTypeConverter.cs

@ -0,0 +1,28 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
using System.ComponentModel;
using System.Globalization;
using Avalonia.Media;
using Portable.Xaml.ComponentModel;
namespace Avalonia.Markup.Xaml.Converters
{
public class FontFamilyTypeConverter : TypeConverter
{
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
{
return sourceType == typeof(string);
}
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
var s = (string)value;
return FontFamily.Parse(s, context.GetBaseUri());
}
}
}

57
src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs

@ -909,8 +909,17 @@ namespace Avalonia.Win32.Interop
public enum ClassLongIndex : int public enum ClassLongIndex : int
{ {
GCL_HCURSOR = -12, GCLP_MENUNAME = -8,
GCL_HICON = -14 GCLP_HBRBACKGROUND = -10,
GCLP_HCURSOR = -12,
GCLP_HICON = -14,
GCLP_HMODULE = -16,
GCL_CBWNDEXTRA = -18,
GCL_CBCLSEXTRA = -20,
GCLP_WNDPROC = -24,
GCL_STYLE = -26,
GCLP_HICONSM = -34,
GCW_ATOM = -32
} }
[DllImport("user32.dll", EntryPoint = "SetClassLongPtr")] [DllImport("user32.dll", EntryPoint = "SetClassLongPtr")]
@ -929,6 +938,20 @@ namespace Avalonia.Win32.Interop
return SetClassLong64(hWnd, nIndex, dwNewLong); return SetClassLong64(hWnd, nIndex, dwNewLong);
} }
public static IntPtr GetClassLongPtr(IntPtr hWnd, int nIndex)
{
if (IntPtr.Size > 4)
return GetClassLongPtr64(hWnd, nIndex);
else
return new IntPtr(GetClassLongPtr32(hWnd, nIndex));
}
[DllImport("user32.dll", EntryPoint = "GetClassLong")]
public static extern uint GetClassLongPtr32(IntPtr hWnd, int nIndex);
[DllImport("user32.dll", EntryPoint = "GetClassLongPtr")]
public static extern IntPtr GetClassLongPtr64(IntPtr hWnd, int nIndex);
[DllImport("user32.dll", EntryPoint = "SetCursor")] [DllImport("user32.dll", EntryPoint = "SetCursor")]
internal static extern IntPtr SetCursor(IntPtr hCursor); internal static extern IntPtr SetCursor(IntPtr hCursor);
@ -1172,6 +1195,8 @@ namespace Avalonia.Win32.Interop
public int right; public int right;
public int bottom; public int bottom;
public int Width => right - left;
public int Height => bottom - top;
public RECT(Rect rect) public RECT(Rect rect)
{ {
left = (int)rect.X; left = (int)rect.X;
@ -1179,6 +1204,34 @@ namespace Avalonia.Win32.Interop
right = (int)(rect.X + rect.Width); right = (int)(rect.X + rect.Width);
bottom = (int)(rect.Y + rect.Height); bottom = (int)(rect.Y + rect.Height);
} }
public void Offset(POINT pt)
{
left += pt.X;
right += pt.X;
top += pt.Y;
bottom += pt.Y;
}
}
[StructLayout(LayoutKind.Sequential)]
public struct WINDOWPOS
{
public IntPtr hwnd;
public IntPtr hwndInsertAfter;
public int x;
public int y;
public int cx;
public int cy;
public uint flags;
}
[StructLayout(LayoutKind.Sequential)]
public struct NCCALCSIZE_PARAMS
{
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)]
public RECT[] rgrc;
public WINDOWPOS lppos;
} }
public struct TRACKMOUSEEVENT public struct TRACKMOUSEEVENT

10
src/Windows/Avalonia.Win32/PopupImpl.cs

@ -24,7 +24,7 @@ namespace Avalonia.Win32
UnmanagedMethods.WindowStyles.WS_EX_TOOLWINDOW | UnmanagedMethods.WindowStyles.WS_EX_TOOLWINDOW |
UnmanagedMethods.WindowStyles.WS_EX_TOPMOST; UnmanagedMethods.WindowStyles.WS_EX_TOPMOST;
return UnmanagedMethods.CreateWindowEx( var result = UnmanagedMethods.CreateWindowEx(
(int)exStyle, (int)exStyle,
atom, atom,
null, null,
@ -37,6 +37,14 @@ namespace Avalonia.Win32
IntPtr.Zero, IntPtr.Zero,
IntPtr.Zero, IntPtr.Zero,
IntPtr.Zero); IntPtr.Zero);
var classes = (int)UnmanagedMethods.GetClassLongPtr(result, (int)UnmanagedMethods.ClassLongIndex.GCL_STYLE);
classes |= (int)UnmanagedMethods.ClassStyles.CS_DROPSHADOW;
UnmanagedMethods.SetClassLong(result, UnmanagedMethods.ClassLongIndex.GCL_STYLE, new IntPtr(classes));
return result;
} }
protected override IntPtr WndProc(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam) protected override IntPtr WndProc(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam)

184
src/Windows/Avalonia.Win32/WindowImpl.cs

@ -35,6 +35,7 @@ namespace Avalonia.Win32
private bool _decorated = true; private bool _decorated = true;
private bool _resizable = true; private bool _resizable = true;
private bool _topmost = false; private bool _topmost = false;
private bool _taskbarIcon = true;
private double _scaling = 1; private double _scaling = 1;
private WindowState _showWindowState; private WindowState _showWindowState;
private WindowState _lastWindowState; private WindowState _lastWindowState;
@ -94,17 +95,25 @@ namespace Avalonia.Win32
{ {
get get
{ {
var style = UnmanagedMethods.GetWindowLong(_hwnd, (int)UnmanagedMethods.WindowLongParam.GWL_STYLE); if (_decorated)
var exStyle = UnmanagedMethods.GetWindowLong(_hwnd, (int)UnmanagedMethods.WindowLongParam.GWL_EXSTYLE);
var padding = new UnmanagedMethods.RECT();
if (UnmanagedMethods.AdjustWindowRectEx(ref padding, style, false, exStyle))
{ {
return new Thickness(-padding.left, -padding.top, padding.right, padding.bottom); var style = UnmanagedMethods.GetWindowLong(_hwnd, (int)UnmanagedMethods.WindowLongParam.GWL_STYLE);
var exStyle = UnmanagedMethods.GetWindowLong(_hwnd, (int)UnmanagedMethods.WindowLongParam.GWL_EXSTYLE);
var padding = new RECT();
if (UnmanagedMethods.AdjustWindowRectEx(ref padding, style, false, exStyle))
{
return new Thickness(-padding.left, -padding.top, padding.right, padding.bottom);
}
else
{
throw new Win32Exception();
}
} }
else else
{ {
throw new Win32Exception(); return new Thickness();
} }
} }
} }
@ -147,8 +156,7 @@ namespace Avalonia.Win32
if (value != ClientSize) if (value != ClientSize)
{ {
value *= Scaling; value *= Scaling;
value += BorderThickness;
UnmanagedMethods.SetWindowPos( UnmanagedMethods.SetWindowPos(
_hwnd, _hwnd,
IntPtr.Zero, IntPtr.Zero,
@ -265,63 +273,7 @@ namespace Avalonia.Win32
return; return;
} }
var style = (UnmanagedMethods.WindowStyles)UnmanagedMethods.GetWindowLong(_hwnd, (int)UnmanagedMethods.WindowLongParam.GWL_STYLE); UpdateWMStyles(()=> _decorated = value);
style |= UnmanagedMethods.WindowStyles.WS_OVERLAPPEDWINDOW;
if (!value)
{
style ^= UnmanagedMethods.WindowStyles.WS_OVERLAPPEDWINDOW;
}
UnmanagedMethods.RECT windowRect;
UnmanagedMethods.GetWindowRect(_hwnd, out windowRect);
Rect newRect;
var oldThickness = BorderThickness;
UnmanagedMethods.SetWindowLong(_hwnd, (int)UnmanagedMethods.WindowLongParam.GWL_STYLE, (uint)style);
if (value)
{
var thickness = BorderThickness;
newRect = new Rect(
windowRect.left - thickness.Left,
windowRect.top - thickness.Top,
(windowRect.right - windowRect.left) + (thickness.Left + thickness.Right),
(windowRect.bottom - windowRect.top) + (thickness.Top + thickness.Bottom));
}
else
{
newRect = new Rect(
windowRect.left + oldThickness.Left,
windowRect.top + oldThickness.Top,
(windowRect.right - windowRect.left) - (oldThickness.Left + oldThickness.Right),
(windowRect.bottom - windowRect.top) - (oldThickness.Top + oldThickness.Bottom));
}
UnmanagedMethods.SetWindowPos(_hwnd, IntPtr.Zero, (int)newRect.X, (int)newRect.Y, (int)newRect.Width,
(int)newRect.Height,
UnmanagedMethods.SetWindowPosFlags.SWP_NOZORDER | UnmanagedMethods.SetWindowPosFlags.SWP_NOACTIVATE);
_decorated = value;
if(_decorated)
{
if (_resizable)
{
// If we switch decorations back on we need to restore WS_SizeFrame.
_resizable = false;
CanResize(true);
}
else
{
_resizable = true;
CanResize(false);
}
}
} }
public void Invalidate(Rect rect) public void Invalidate(Rect rect)
@ -432,7 +384,7 @@ namespace Avalonia.Win32
public void SetCursor(IPlatformHandle cursor) public void SetCursor(IPlatformHandle cursor)
{ {
var hCursor = cursor?.Handle ?? DefaultCursor; var hCursor = cursor?.Handle ?? DefaultCursor;
UnmanagedMethods.SetClassLong(_hwnd, UnmanagedMethods.ClassLongIndex.GCL_HCURSOR, hCursor); UnmanagedMethods.SetClassLong(_hwnd, UnmanagedMethods.ClassLongIndex.GCLP_HCURSOR, hCursor);
if (_owner.IsPointerOver) if (_owner.IsPointerOver)
UnmanagedMethods.SetCursor(hCursor); UnmanagedMethods.SetCursor(hCursor);
@ -486,6 +438,13 @@ namespace Avalonia.Win32
return IntPtr.Zero; return IntPtr.Zero;
case WindowsMessage.WM_NCCALCSIZE:
if (ToInt32(wParam) == 1 && !_decorated)
{
return IntPtr.Zero;
}
break;
case UnmanagedMethods.WindowsMessage.WM_CLOSE: case UnmanagedMethods.WindowsMessage.WM_CLOSE:
bool? preventClosing = Closing?.Invoke(); bool? preventClosing = Closing?.Invoke();
if (preventClosing == true) if (preventClosing == true)
@ -646,6 +605,20 @@ namespace Avalonia.Win32
new Point(0, 0), GetMouseModifiers(wParam)); new Point(0, 0), GetMouseModifiers(wParam));
break; break;
case WindowsMessage.WM_NCPAINT:
if (!_decorated)
{
return IntPtr.Zero;
}
break;
case WindowsMessage.WM_NCACTIVATE:
if (!_decorated)
{
return new IntPtr(1);
}
break;
case UnmanagedMethods.WindowsMessage.WM_PAINT: case UnmanagedMethods.WindowsMessage.WM_PAINT:
UnmanagedMethods.PAINTSTRUCT ps; UnmanagedMethods.PAINTSTRUCT ps;
if (UnmanagedMethods.BeginPaint(_hwnd, out ps) != IntPtr.Zero) if (UnmanagedMethods.BeginPaint(_hwnd, out ps) != IntPtr.Zero)
@ -688,7 +661,7 @@ namespace Avalonia.Win32
MINMAXINFO mmi = Marshal.PtrToStructure<UnmanagedMethods.MINMAXINFO>(lParam); MINMAXINFO mmi = Marshal.PtrToStructure<UnmanagedMethods.MINMAXINFO>(lParam);
if (_minSize.Width > 0) if (_minSize.Width > 0)
mmi.ptMinTrackSize.X = (int)((_minSize.Width * Scaling) + BorderThickness.Left + BorderThickness.Right); mmi.ptMinTrackSize.X = (int)((_minSize.Width * Scaling) + BorderThickness.Left + BorderThickness.Right);
if (_minSize.Height > 0) if (_minSize.Height > 0)
@ -859,7 +832,7 @@ namespace Avalonia.Win32
{ {
MONITORINFO monitorInfo = MONITORINFO.Create(); MONITORINFO monitorInfo = MONITORINFO.Create();
if (GetMonitorInfo(monitor,ref monitorInfo)) if (GetMonitorInfo(monitor, ref monitorInfo))
{ {
RECT rcMonitorArea = monitorInfo.rcMonitor; RECT rcMonitorArea = monitorInfo.rcMonitor;
@ -888,13 +861,22 @@ namespace Avalonia.Win32
return (int)(ptr.ToInt64() & 0xffffffff); return (int)(ptr.ToInt64() & 0xffffffff);
} }
public void ShowTaskbarIcon(bool value) public void ShowTaskbarIcon(bool value)
{ {
if (_taskbarIcon == value)
{
return;
}
_taskbarIcon = value;
var style = (UnmanagedMethods.WindowStyles)UnmanagedMethods.GetWindowLong(_hwnd, (int)UnmanagedMethods.WindowLongParam.GWL_EXSTYLE); var style = (UnmanagedMethods.WindowStyles)UnmanagedMethods.GetWindowLong(_hwnd, (int)UnmanagedMethods.WindowLongParam.GWL_EXSTYLE);
style &= ~(UnmanagedMethods.WindowStyles.WS_VISIBLE); style &= ~(UnmanagedMethods.WindowStyles.WS_VISIBLE);
style |= UnmanagedMethods.WindowStyles.WS_EX_TOOLWINDOW; style |= UnmanagedMethods.WindowStyles.WS_EX_TOOLWINDOW;
if (value) if (value)
style |= UnmanagedMethods.WindowStyles.WS_EX_APPWINDOW; style |= UnmanagedMethods.WindowStyles.WS_EX_APPWINDOW;
else else
@ -910,26 +892,68 @@ namespace Avalonia.Win32
} }
} }
public void CanResize(bool value) private void UpdateWMStyles(Action change)
{ {
if (value == _resizable) var oldDecorated = _decorated;
var oldThickness = BorderThickness;
change();
var style = (WindowStyles)GetWindowLong(_hwnd, (int)WindowLongParam.GWL_STYLE);
const WindowStyles controlledFlags = WindowStyles.WS_OVERLAPPEDWINDOW;
style = style | controlledFlags ^ controlledFlags;
style |= WindowStyles.WS_OVERLAPPEDWINDOW;
if (!_decorated)
{ {
return; style ^= (WindowStyles.WS_CAPTION | WindowStyles.WS_SYSMENU);
} }
if (_decorated) if (!_resizable)
{ {
var style = (UnmanagedMethods.WindowStyles)UnmanagedMethods.GetWindowLong(_hwnd, (int)UnmanagedMethods.WindowLongParam.GWL_STYLE); style ^= (WindowStyles.WS_SIZEFRAME);
}
if (value) GetClientRect(_hwnd, out var oldClientRect);
style |= UnmanagedMethods.WindowStyles.WS_SIZEFRAME; var oldClientRectOrigin = new UnmanagedMethods.POINT();
else ClientToScreen(_hwnd, ref oldClientRectOrigin);
style &= ~(UnmanagedMethods.WindowStyles.WS_SIZEFRAME); oldClientRect.Offset(oldClientRectOrigin);
SetWindowLong(_hwnd, (int)WindowLongParam.GWL_STYLE, (uint)style);
UnmanagedMethods.GetWindowRect(_hwnd, out var windowRect);
bool frameUpdated = false;
if (oldDecorated != _decorated)
{
var newRect = oldClientRect;
if (_decorated)
AdjustWindowRectEx(ref newRect, (uint)style, false,
GetWindowLong(_hwnd, (int)WindowLongParam.GWL_EXSTYLE));
SetWindowPos(_hwnd, IntPtr.Zero, newRect.left, newRect.top, newRect.Width, newRect.Height,
SetWindowPosFlags.SWP_NOZORDER | SetWindowPosFlags.SWP_NOACTIVATE | SetWindowPosFlags.SWP_FRAMECHANGED);
frameUpdated = true;
}
UnmanagedMethods.SetWindowLong(_hwnd, (int)UnmanagedMethods.WindowLongParam.GWL_STYLE, (uint)style); if (!frameUpdated)
SetWindowPos(_hwnd, IntPtr.Zero, 0, 0, 0, 0,
SetWindowPosFlags.SWP_FRAMECHANGED | SetWindowPosFlags.SWP_NOZORDER |
SetWindowPosFlags.SWP_NOACTIVATE
| SetWindowPosFlags.SWP_NOMOVE | SetWindowPosFlags.SWP_NOSIZE);
}
public void CanResize(bool value)
{
if (value == _resizable)
{
return;
} }
_resizable = value; UpdateWMStyles(()=> _resizable = value);
} }
public void SetTopmost(bool value) public void SetTopmost(bool value)

8
tests/Avalonia.Visuals.UnitTests/Media/Fonts/FontFamilyKeyTests.cs

@ -22,9 +22,7 @@ namespace Avalonia.Visuals.UnitTests.Media.Fonts
var fontFamilyKey = new FontFamilyKey(source); var fontFamilyKey = new FontFamilyKey(source);
Assert.Equal(new Uri("resm:Avalonia.Visuals.UnitTests"), fontFamilyKey.Location); Assert.Equal(new Uri("resm:Avalonia.Visuals.UnitTests"), fontFamilyKey.Source);
Assert.Null(fontFamilyKey.FileName);
} }
[Fact] [Fact]
@ -34,9 +32,7 @@ namespace Avalonia.Visuals.UnitTests.Media.Fonts
var fontFamilyKey = new FontFamilyKey(source); var fontFamilyKey = new FontFamilyKey(source);
Assert.Equal(new Uri("resm:Avalonia.Visuals.UnitTests"), fontFamilyKey.Location); Assert.Equal(new Uri("resm:Avalonia.Visuals.UnitTests.MyFont.ttf"), fontFamilyKey.Source);
Assert.Equal("MyFont.ttf", fontFamilyKey.FileName);
} }
} }
} }

84
tests/Avalonia.Visuals.UnitTests/Rendering/ImmediateRendererTests.cs

@ -1,8 +1,11 @@
using System.Collections.Generic; using System.Collections.Generic;
using Avalonia.Collections; using Avalonia.Collections;
using Avalonia.Controls;
using Avalonia.Layout;
using Avalonia.Media; using Avalonia.Media;
using Avalonia.Platform; using Avalonia.Platform;
using Avalonia.Rendering; using Avalonia.Rendering;
using Avalonia.UnitTests;
using Avalonia.VisualTree; using Avalonia.VisualTree;
using Moq; using Moq;
using Xunit; using Xunit;
@ -94,5 +97,86 @@ namespace Avalonia.Visuals.UnitTests.Rendering
//then new position //then new position
Assert.Equal(new Rect(100, 100, 100, 100), invalidationCalls[2]); Assert.Equal(new Rect(100, 100, 100, 100), invalidationCalls[2]);
} }
[Fact]
public void Should_Render_Child_In_Parent_With_RenderTransform()
{
var targetMock = new Mock<Control>() { CallBase = true };
var target = targetMock.Object;
target.Width = 100;
target.Height = 50;
var child = new Panel()
{
RenderTransform = new RotateTransform() { Angle = 90 },
Children =
{
new Panel()
{
Children =
{
target
}
}
}
};
var visualTarget = targetMock.As<IVisual>();
int rendered = 0;
visualTarget.Setup(v => v.Render(It.IsAny<DrawingContext>())).Callback(() => rendered++);
var root = new TestRoot(child);
root.Renderer = new ImmediateRenderer(root);
root.LayoutManager.ExecuteInitialLayoutPass(root);
root.Measure(new Size(50, 100));
root.Arrange(new Rect(new Size(50, 100)));
root.Renderer.Paint(root.Bounds);
Assert.Equal(1, rendered);
}
[Fact]
public void Should_Render_Child_In_Parent_With_RenderTransform2()
{
var targetMock = new Mock<Control>() { CallBase = true };
var target = targetMock.Object;
target.Width = 100;
target.Height = 50;
target.HorizontalAlignment = HorizontalAlignment.Center;
target.VerticalAlignment = VerticalAlignment.Center;
var child = new Panel()
{
RenderTransform = new RotateTransform() { Angle = 90 },
Children =
{
new Panel()
{
Children =
{
target
}
}
}
};
var visualTarget = targetMock.As<IVisual>();
int rendered = 0;
visualTarget.Setup(v => v.Render(It.IsAny<DrawingContext>())).Callback(() => rendered++);
var root = new TestRoot(child);
root.Renderer = new ImmediateRenderer(root);
root.LayoutManager.ExecuteInitialLayoutPass(root);
root.Measure(new Size(300, 100));
root.Arrange(new Rect(new Size(300, 100)));
root.Renderer.Paint(root.Bounds);
Assert.Equal(1, rendered);
}
} }
} }

Loading…
Cancel
Save