@ -0,0 +1,13 @@
|
||||
# Set the default behavior, in case people don't have core.autocrlf set.
|
||||
* text=auto
|
||||
|
||||
# Declare files that will always have CRLF line endings on checkout.
|
||||
*.cs text eol=crlf
|
||||
*.xaml text eol=crlf
|
||||
*.resw text eol=crlf
|
||||
*.csproj text eol=crlf
|
||||
*.sln text eol=crlf
|
||||
|
||||
# Denote all files that are truly binary and should not be modified.
|
||||
*.png binary
|
||||
*.jpg binary
|
||||
@ -0,0 +1,12 @@
|
||||
# These are supported funding model platforms
|
||||
|
||||
github: 0x7c13
|
||||
patreon: # Replace with a single Patreon username
|
||||
open_collective: # Replace with a single Open Collective username
|
||||
ko_fi: jackieliu
|
||||
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
|
||||
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
|
||||
liberapay: # Replace with a single Liberapay username
|
||||
issuehunt: # Replace with a single IssueHunt username
|
||||
otechie: # Replace with a single Otechie username
|
||||
custom: paypal.me/jackil
|
||||
@ -0,0 +1,27 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Create a report to help us improve
|
||||
title: "[Bug]"
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Describe the bug**
|
||||
A clear and concise description of what the bug is.
|
||||
|
||||
**To Reproduce**
|
||||
Steps to reproduce the behavior.
|
||||
|
||||
**Expected behavior**
|
||||
A clear and concise description of what you expected to happen.
|
||||
|
||||
**Screenshots**
|
||||
If applicable, add screenshots to help explain your problem.
|
||||
|
||||
**Desktop (please complete the following information):**
|
||||
- OS: [e.g. Windows 10 1809 17763.593]
|
||||
- Version [e.g. v0.9.3.0]
|
||||
|
||||
**Additional context**
|
||||
Add any other context about the problem here.
|
||||
@ -0,0 +1,20 @@
|
||||
---
|
||||
name: Feature request
|
||||
about: Suggest an idea for this project
|
||||
title: "[Feature request]"
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Is your feature request related to a problem? Please describe.**
|
||||
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
||||
|
||||
**Describe the solution you'd like**
|
||||
A clear and concise description of what you want to happen.
|
||||
|
||||
**Describe alternatives you've considered**
|
||||
A clear and concise description of any alternative solutions or features you've considered.
|
||||
|
||||
**Additional context**
|
||||
Add any other context or screenshots about the feature request here.
|
||||
@ -0,0 +1,25 @@
|
||||
<!-- Add a brief overview here of the feature/bug & fix. -->
|
||||
|
||||
## PR Type
|
||||
What kind of change does this PR introduce?
|
||||
<!-- Please uncomment one or more that apply to this PR and apply required prefix to the title. -->
|
||||
|
||||
<!-- - Bugfix -->
|
||||
<!-- Prefix title with "fix: " -->
|
||||
|
||||
<!-- - Feature -->
|
||||
<!-- Prefix title with "feat: " -->
|
||||
|
||||
<!-- - Translation -->
|
||||
<!-- Prefix title with "lang: " -->
|
||||
|
||||
<!-- - Documentation content changes -->
|
||||
<!-- Prefix title with "doc: " -->
|
||||
|
||||
<!-- - CI/CD pipeline changes -->
|
||||
<!-- Prefix title with "ci: " -->
|
||||
|
||||
<!-- - Other... Please describe: -->
|
||||
<!-- Prefix title with "other: " or custom label with conventional commit format: https://www.conventionalcommits.org/en/v1.0.0/ -->
|
||||
|
||||
## Other information
|
||||
@ -0,0 +1,13 @@
|
||||
{
|
||||
"conventionalCommitsParserOptions": {
|
||||
"revertPattern": "/^(?:Revert|revert:)\\s\"?([\\s\\S]+?)\"?\\s*This reverts commit (\\w*)\\./i",
|
||||
"issuePrefixes": [ "#", "OLDARCH-" ]
|
||||
},
|
||||
"handleBarsOptions": {
|
||||
"setupFile": null,
|
||||
"template": ".github/RELEASE_TEMPLATE/changelog_template.hbs",
|
||||
"compileOptions": { "noEscape": true }
|
||||
},
|
||||
"breakingChangesPattern": "/^breaking\\s+change$/gim",
|
||||
"hostname": "https://github.com"
|
||||
}
|
||||
@ -0,0 +1,43 @@
|
||||
{{#with release}}
|
||||
## [{{name}}]({{href}})
|
||||
{{/with}}
|
||||
|
||||
{{#commit-list commits heading='### 💥 Breaking Changes' breaking=true }}
|
||||
- {{#if scope}} **{{scope}}:** {{/if}}{{subject}} ([`{{shorthash}}`]({{html_url}}))
|
||||
{{/commit-list}}
|
||||
|
||||
{{#commit-list commits heading='### ✨ Features' type='feat' excludeBreaking=true}}
|
||||
- {{#if scope}} **{{scope}}:** {{/if}}{{subject}} ([`{{shorthash}}`]({{html_url}}))
|
||||
{{/commit-list}}
|
||||
|
||||
{{#commit-list commits heading='### 🐛 Fixes' type='fix' excludeBreaking=true }}
|
||||
- {{#if scope}} **{{scope}}:** {{/if}}{{subject}} ([`{{shorthash}}`]({{html_url}}))
|
||||
{{/commit-list}}
|
||||
|
||||
{{#commit-list commits heading='### 🔥 Refactorings' type='refactor' excludeBreaking=true }}
|
||||
- {{#if scope}} **{{scope}}:** {{/if}}{{subject}} ([`{{shorthash}}`]({{html_url}}))
|
||||
{{/commit-list}}
|
||||
|
||||
{{#commit-list commits heading='### 🐎 Performance Improvements' type='perf' excludeBreaking=true }}
|
||||
- {{#if scope}} **{{scope}}:** {{/if}}{{subject}} ([`{{shorthash}}`]({{html_url}}))
|
||||
{{/commit-list}}
|
||||
|
||||
{{#commit-list commits heading='### 🛠 Maintenance' types='chore,ci' excludeBreaking=true }}
|
||||
- {{#if scope}} **{{scope}}:** {{/if}}{{subject}} ([`{{shorthash}}`]({{html_url}}))
|
||||
{{/commit-list}}
|
||||
|
||||
{{#commit-list commits heading='### ✅ Tests' type='test' excludeBreaking=true }}
|
||||
- {{#if scope}} **{{scope}}:** {{/if}}{{subject}} ([`{{shorthash}}`]({{html_url}}))
|
||||
{{/commit-list}}
|
||||
|
||||
{{#commit-list commits heading='### 📚 Documentation' type='doc,docs' excludeBreaking=true }}
|
||||
- {{#if scope}} **{{scope}}:** {{/if}}{{subject}} ([`{{shorthash}}`]({{html_url}}))
|
||||
{{/commit-list}}
|
||||
|
||||
{{#commit-list commits heading='### 💄 Style' type='style' excludeBreaking=true }}
|
||||
- {{#if scope}} **{{scope}}:** {{/if}}{{subject}} ([`{{shorthash}}`]({{html_url}}))
|
||||
{{/commit-list}}
|
||||
|
||||
{{#commit-list commits heading='### 📢 Translations' type='lang,trans' excludeBreaking=true }}
|
||||
- {{#if scope}} **{{scope}}:** {{/if}}{{subject}} ([`{{shorthash}}`]({{html_url}}))
|
||||
{{/commit-list}}
|
||||
@ -0,0 +1,19 @@
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: "github-actions"
|
||||
# default location of `.github/workflows`
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
commit-message:
|
||||
prefix: 'action-deps: '
|
||||
|
||||
- package-ecosystem: "nuget"
|
||||
# location of package manifests
|
||||
directory: "/src/Notepads"
|
||||
schedule:
|
||||
interval: "daily"
|
||||
commit-message:
|
||||
prefix: 'nuget-deps: '
|
||||
|
||||
# Built with ❤ by [Pipeline Foundation](https://pipeline.foundation)
|
||||
@ -0,0 +1,4 @@
|
||||
label-alias:
|
||||
bug: 'bug'
|
||||
feature_request: 'enhancement'
|
||||
question: 'question'
|
||||
@ -0,0 +1,128 @@
|
||||
name: Code scanning alerts bulk dismissal
|
||||
|
||||
on:
|
||||
workflow_run:
|
||||
workflows: [ "Notepads CI/CD Pipeline" ]
|
||||
types:
|
||||
- completed
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
type:
|
||||
description: Type of filter to use ("path" for using path and "desc" for using description)
|
||||
required: true
|
||||
default: 'path'
|
||||
reason:
|
||||
description: Reason for dismissal ("fp" for "false positive", "wf" for "won't fix" and "ut" for "used in tests")
|
||||
required: true
|
||||
default: 'wf'
|
||||
|
||||
jobs:
|
||||
setup:
|
||||
runs-on: windows-latest
|
||||
outputs:
|
||||
matrix: ${{ steps.set_filter_matrix.outputs.matrix }}
|
||||
steps:
|
||||
- name: Setup filter matrix
|
||||
id: set_filter_matrix
|
||||
shell: pwsh
|
||||
run: |
|
||||
$FILTER_TYPE = $env:FILTER_TYPE
|
||||
if ( !( $env:FILTER_TYPE -ieq 'path' ) -And !( $env:FILTER_TYPE -ieq 'desc' ) ) {
|
||||
$FILTER_TYPE = 'path'
|
||||
}
|
||||
|
||||
switch ( $env:REASON ) {
|
||||
fp {
|
||||
$REASON = "false positive"
|
||||
}
|
||||
wf {
|
||||
$REASON = "won't fix"
|
||||
}
|
||||
ut {
|
||||
$REASON = "used in tests"
|
||||
}
|
||||
default {
|
||||
$REASON = "won't fix"
|
||||
}
|
||||
}
|
||||
|
||||
if ( $FILTER_TYPE -ieq 'path' ) {
|
||||
$MATRIX = @{
|
||||
include = @(
|
||||
@{
|
||||
filter = "*/obj/*"
|
||||
}
|
||||
)
|
||||
}
|
||||
} elseif ( $FILTER_TYPE -ieq 'desc' ) {
|
||||
$MATRIX = @{
|
||||
include = @(
|
||||
@{
|
||||
filter = "Calls to unmanaged code"
|
||||
},
|
||||
@{
|
||||
filter = "Unmanaged code"
|
||||
}
|
||||
)
|
||||
}
|
||||
} else {
|
||||
throw "Invalid filter type argument"
|
||||
}
|
||||
|
||||
$MATRIX.include | Foreach-Object {
|
||||
$_.Add('type',"$FILTER_TYPE")
|
||||
$_.Add('reason',"$REASON")
|
||||
}
|
||||
echo "::set-output name=matrix::$($MATRIX | ConvertTo-Json -depth 32 -Compress)"
|
||||
env:
|
||||
FILTER_TYPE: ${{ github.event.inputs.type }}
|
||||
REASON: ${{ github.event.inputs.reason }}
|
||||
dismiss-alerts:
|
||||
name: Dismiss alerts
|
||||
needs: setup
|
||||
runs-on: windows-latest
|
||||
strategy:
|
||||
matrix: ${{ fromJson(needs.setup.outputs.matrix) }}
|
||||
env:
|
||||
# Settings
|
||||
OWNER: ${{ github.repository_owner }} # verbatim from URL
|
||||
PROJECT_NAME: ${{ github.event.repository.name }} # verbatim from URL
|
||||
ACCESS_TOKEN: ${{ secrets.CSA_ACCESS_TOKEN }} # requires security_events read/write permissions
|
||||
DISMISS_REASON: ${{ matrix.reason }} # "false positive", "won't fix" or "used in tests".
|
||||
ALERTS_PER_PAGE: 100 # maximum is 100
|
||||
FILTER: ${{ matrix.filter }}
|
||||
FILTER_TYPE: ${{ matrix.type }}
|
||||
steps:
|
||||
- name: Run automation
|
||||
id: run_automation
|
||||
shell: pwsh
|
||||
run: |
|
||||
$HEADERS = @{
|
||||
Authorization = 'Basic {0}' -f [System.Convert]::ToBase64String([char[]]"$($env:OWNER):$($env:ACCESS_TOKEN)")
|
||||
Accept = 'application/vnd.github.v3+json'
|
||||
}
|
||||
|
||||
$page = 1
|
||||
$FETCH_URL = "https://api.github.com/repos/$env:OWNER/$env:PROJECT_NAME/code-scanning/alerts?state=open&page={0}&per_page=$env:ALERTS_PER_PAGE"
|
||||
$LIST_OF_ALERTS = Invoke-RestMethod -Method Get -Headers $HEADERS -Uri $($FETCH_URL -f $page)
|
||||
while ( $LIST_OF_ALERTS -ne $null ) {
|
||||
if ( $env:FILTER_TYPE -ieq 'path' ) {
|
||||
$MATCHES += $($LIST_OF_ALERTS | Where-Object { $_.most_recent_instance.location.path -like "$env:FILTER" })
|
||||
} else {
|
||||
$MATCHES += $($LIST_OF_ALERTS | Where-Object { $_.rule.description -like "$env:FILTER" })
|
||||
}
|
||||
|
||||
$page += 1
|
||||
$LIST_OF_ALERTS = Invoke-RestMethod -Method Get -Headers $HEADERS -Uri $($FETCH_URL -f $page)
|
||||
}
|
||||
|
||||
$ALERT_URL = "https://api.github.com/repos/$env:OWNER/$env:PROJECT_NAME/code-scanning/alerts/{0}"
|
||||
$BODY = @{
|
||||
state = 'dismissed'
|
||||
dismissed_reason = "$env:DISMISS_REASON"
|
||||
} | ConvertTo-Json
|
||||
foreach ($index in $MATCHES.number) {
|
||||
Invoke-RestMethod -Method Patch -Headers $HEADERS -Uri $($ALERT_URL -f $index) -Body $BODY
|
||||
}
|
||||
|
||||
# Built with ❤ by [Pipeline Foundation](https://pipeline.foundation)
|
||||
@ -0,0 +1,402 @@
|
||||
name: Notepads CI/CD Pipeline
|
||||
|
||||
on:
|
||||
push:
|
||||
#paths-ignore:
|
||||
#- '**.md'
|
||||
#- 'ScreenShots/**'
|
||||
#- '.whitesource'
|
||||
#- 'azure-pipelines.yml'
|
||||
#- '.github/**'
|
||||
#- '!.github/workflows/main.yml'
|
||||
branches-ignore:
|
||||
# PRs made by bots trigger both 'push' and 'pull_request' event, ignore 'push' event in that case
|
||||
- 'dependabot**'
|
||||
- 'imgbot**'
|
||||
tags-ignore:
|
||||
- '**'
|
||||
pull_request:
|
||||
paths-ignore:
|
||||
- '**.md'
|
||||
- 'ScreenShots/**'
|
||||
- '.whitesource'
|
||||
- 'azure-pipelines.yml'
|
||||
- '.github/**'
|
||||
- '!.github/workflows/main.yml'
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
param:
|
||||
description: Optional parameter for additional actions
|
||||
# Type '(major|maj) (realease|rel)' or '(minor|min) (realease|rel)' or release for major,miner,patch release respectively
|
||||
# Or explicitly provide version number to create release with that version
|
||||
required: false
|
||||
schedule:
|
||||
- cron: '0 8 * * *'
|
||||
|
||||
jobs:
|
||||
setup:
|
||||
runs-on: windows-latest
|
||||
outputs:
|
||||
matrix: ${{ steps.set_matrix.outputs.matrix }}
|
||||
steps:
|
||||
- name: Setup strategy matrix
|
||||
id: set_matrix
|
||||
shell: pwsh
|
||||
run: |
|
||||
$MATRIX = @{
|
||||
include = @( [ordered]@{
|
||||
configuration= "Debug"
|
||||
appxBundlePlatforms = "x86|x64"
|
||||
oldVersion = ""
|
||||
newVersion = ""
|
||||
debug = $true
|
||||
runCodeqlAnalysis = $false
|
||||
runSonarCloudScan = $false
|
||||
}, [ordered]@{
|
||||
configuration= "Release"
|
||||
appxBundlePlatforms= "x86|x64|ARM64"
|
||||
oldVersion = ""
|
||||
newVersion = ""
|
||||
debug = $true
|
||||
runCodeqlAnalysis= $false
|
||||
runSonarCloudScan= $false
|
||||
}, [ordered]@{
|
||||
configuration= "Production"
|
||||
appxBundlePlatforms= "x86|x64|ARM64"
|
||||
oldVersion = ""
|
||||
newVersion = ""
|
||||
debug = $true
|
||||
runCodeqlAnalysis= $false
|
||||
runSonarCloudScan= $false
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
if ( ( $env:GITHUB_EVENT -eq 'pull_request' ) `
|
||||
-or ( $env:GITHUB_EVENT -eq 'schedule' ) `
|
||||
-or ( $env:FORK -eq 'true' ) ) {
|
||||
$MATRIX.include | Foreach-Object { $_.runSonarCloudScan = $false }
|
||||
}
|
||||
|
||||
if ( ( $env:GITHUB_EVENT -ne 'push' ) `
|
||||
-and ( $env:GITHUB_EVENT -ne 'pull_request' ) ) {
|
||||
$MATRIX.include = @($MATRIX.include | Where-Object { $_.configuration -eq "$env:RELEASE_CONFIGURATION" })
|
||||
|
||||
if ( ( $env:GITHUB_EVENT -eq 'workflow_dispatch' ) `
|
||||
-and ( $env:GITHUB_REF -eq 'refs/heads/master' ) ) {
|
||||
$FETCH_URL = "https://api.github.com/repos/$env:GIT_REPOSITORY/tags?per_page=1"
|
||||
$OLD_VER = [System.Version]::Parse($(Invoke-RestMethod -Method Get -Uri $FETCH_URL).name -replace 'v')
|
||||
|
||||
[System.Int32[]]$VER_INPUT = $($env:PARAM -replace '[a-zA-Z]| ').Split('.')
|
||||
if ( ( $VER_INPUT.Count -gt 1 ) -or ( $VER_INPUT[0] -gt 0 ) ) {
|
||||
$NEW_VER = [System.Version]::new($VER_INPUT[0],`
|
||||
(if ( $VER_INPUT.Count -ge 1 ) { $VER_INPUT[1] } else { 0 }),`
|
||||
(if ( $VER_INPUT.Count -ge 2 ) { $VER_INPUT[2] } else { 0 }),`
|
||||
(if ( $VER_INPUT.Count -ge 3 ) { $VER_INPUT[3] } else { 0 }))
|
||||
} elseif ( $env:PARAM -match 'rel' ) {
|
||||
if ( $env:PARAM -match 'maj' ) {
|
||||
$NEW_VER = [System.Version]::new($OLD_VER.Major + 1, 0, 0, 0)
|
||||
} elseif ( $env:PARAM -match 'min' ) {
|
||||
$NEW_VER = [System.Version]::new($OLD_VER.Major, $OLD_VER.Minor + 1, 0, 0)
|
||||
} else {
|
||||
$NEW_VER = [System.Version]::new($OLD_VER.Major, $OLD_VER.Minor, $OLD_VER.Build + 1, 0)
|
||||
}
|
||||
}
|
||||
|
||||
if ( ![System.String]::IsNullOrEmpty($OLD_VER) `
|
||||
-and ![System.String]::IsNullOrEmpty($NEW_VER) `
|
||||
-and ( $NEW_VER -gt $OLD_VER ) ) {
|
||||
$MATRIX.include | Foreach-Object { $_.oldVersion = $OLD_VER.ToString() }
|
||||
$MATRIX.include | Foreach-Object { $_.newVersion = $NEW_VER.ToString() }
|
||||
}
|
||||
|
||||
$MATRIX.include | Foreach-Object { $_.runCodeqlAnalysis = $false }
|
||||
$MATRIX.include | Foreach-Object {
|
||||
if ( $_.configuration -eq "$env:RELEASE_CONFIGURATION" ) { $_.release = $true }
|
||||
}
|
||||
} else {
|
||||
$MATRIX.include | Foreach-Object { $_.appxBundlePlatforms = 'x64' }
|
||||
if ( $env:GITHUB_EVENT -ne 'schedule' ) {
|
||||
$MATRIX.include | Foreach-Object { $_.runCodeqlAnalysis = $false }
|
||||
}
|
||||
}
|
||||
}
|
||||
echo "::set-output name=matrix::$($MATRIX | ConvertTo-Json -depth 32 -Compress)"
|
||||
env:
|
||||
FORK: ${{ github.event.repository.fork }}
|
||||
PARAM: ${{ github.event.inputs.param }}
|
||||
GITHUB_REF: ${{ github.ref }}
|
||||
GITHUB_EVENT: ${{ github.event_name }}
|
||||
RELEASE_CONFIGURATION: Production
|
||||
|
||||
ci:
|
||||
needs: setup
|
||||
runs-on: windows-latest
|
||||
strategy:
|
||||
matrix: ${{ fromJson(needs.setup.outputs.matrix) }}
|
||||
outputs:
|
||||
old_version: ${{ matrix.oldVersion }}
|
||||
new_version: ${{ matrix.newVersion }}
|
||||
env:
|
||||
SOLUTION_NAME: src\Notepads.sln
|
||||
CONFIGURATION: ${{ matrix.configuration }}
|
||||
DEFAULT_DIR: ${{ github.workspace }}
|
||||
steps:
|
||||
- if: matrix.runSonarCloudScan
|
||||
name: Set up JDK 11
|
||||
id: Setup_JDK
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
java-version: 1.11
|
||||
|
||||
- name: Setup MSBuild
|
||||
id: setup_msbuild
|
||||
uses: microsoft/setup-msbuild@v2
|
||||
|
||||
- name: Setup NuGet
|
||||
id: setup-nuget
|
||||
uses: NuGet/setup-nuget@v2.0.1
|
||||
|
||||
- name: Checkout repository
|
||||
id: checkout_repo
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 50
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
# Due to the insufficient memory allocated by default, CodeQL sometimes requires more to be manually allocated
|
||||
- if: matrix.runCodeqlAnalysis
|
||||
name: Configure Pagefile
|
||||
id: config_pagefile
|
||||
uses: al-cheb/configure-pagefile-action@v1.4
|
||||
with:
|
||||
minimum-size: 8GB
|
||||
maximum-size: 10GB
|
||||
|
||||
- if: matrix.newVersion != ''
|
||||
name: Bump GitHub tag and Update manifest
|
||||
id: tag_manifest_generator
|
||||
shell: pwsh
|
||||
run: |
|
||||
git config --global user.name $env:GIT_USER_NAME
|
||||
git config --global user.email $env:GIT_USER_EMAIL
|
||||
git tag -a -m "$env:NEW_VERSION_TAG" $env:NEW_VERSION_TAG
|
||||
git push --follow-tags
|
||||
$xml = [xml](Get-Content $env:APPXMANIFEST_PATH)
|
||||
$xml.Package.Identity.Version = $env:NEW_VERSION
|
||||
$xml.save($env:APPXMANIFEST_PATH)
|
||||
env:
|
||||
GIT_USER_NAME: ${{ secrets.GIT_USER_NAME }}
|
||||
GIT_USER_EMAIL: ${{ secrets.GIT_USER_EMAIL }}
|
||||
APPXMANIFEST_PATH: src\Notepads\Package.appxmanifest
|
||||
NEW_VERSION: ${{ matrix.newVersion }}
|
||||
NEW_VERSION_TAG: v${{ matrix.newVersion }}
|
||||
|
||||
- if: matrix.runSonarCloudScan
|
||||
name: Cache SonarCloud packages
|
||||
id: cache_sonar_packages
|
||||
uses: actions/cache@v4.2.3
|
||||
with:
|
||||
path: ~\sonar\cache
|
||||
key: ${{ runner.os }}-sonar
|
||||
restore-keys: ${{ runner.os }}-sonar
|
||||
|
||||
- if: matrix.runSonarCloudScan
|
||||
name: Cache SonarCloud scanner
|
||||
id: cache_sonar_scanner
|
||||
uses: actions/cache@v4.2.3
|
||||
with:
|
||||
path: .\.sonar\scanner
|
||||
key: ${{ runner.os }}-sonar-scanner
|
||||
restore-keys: ${{ runner.os }}-sonar-scanner
|
||||
|
||||
- if: matrix.runSonarCloudScan && steps.cache_sonar_scanner.outputs.cache-hit != 'true'
|
||||
name: Install SonarCloud scanner
|
||||
id: install_sonar_scanner
|
||||
shell: pwsh
|
||||
run: |
|
||||
New-Item -Path .\.sonar\scanner -ItemType Directory
|
||||
dotnet tool update dotnet-sonarscanner --tool-path .\.sonar\scanner
|
||||
|
||||
- if: matrix.runSonarCloudScan
|
||||
name: Initialize SonarCloud scanner
|
||||
id: init_sonar_scanner
|
||||
shell: pwsh
|
||||
run: |
|
||||
$LOWERCASE_REPOSITORY_NAME = "${{ github.event.repository.name }}".ToLower()
|
||||
.\.sonar\scanner\dotnet-sonarscanner begin `
|
||||
/k:"${{ github.repository_owner }}_${{ github.event.repository.name }}" `
|
||||
/o:"$LOWERCASE_REPOSITORY_NAME" `
|
||||
/d:sonar.login="$env:SONAR_TOKEN" `
|
||||
/d:sonar.host.url="https://sonarcloud.io"
|
||||
env:
|
||||
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
|
||||
|
||||
- if: matrix.newVersion != ''
|
||||
name: Create and validate PFX certificate for AppxBundle
|
||||
id: create_validate_pfx_cert
|
||||
shell: pwsh
|
||||
run: |
|
||||
$TARGET_FILE = "$env:DEFAULT_DIR\cert.pfx"
|
||||
$FROM_BASE64_STR = [System.Convert]::FromBase64String($env:BASE64_STR)
|
||||
[System.IO.File]::WriteAllBytes($TARGET_FILE, $FROM_BASE64_STR)
|
||||
|
||||
$FILE_STREAM = [System.IO.File]::OpenRead($TARGET_FILE)
|
||||
$FILE_STREAM.Position = 0
|
||||
$SHA256 = [System.Security.Cryptography.SHA256]::Create()
|
||||
$HASH_BUILDER = [System.Text.StringBuilder]::new()
|
||||
$SHA256.ComputeHash($FILE_STREAM) | ForEach-Object { $HASH_BUILDER.Append($_.ToString("x2")) }
|
||||
if ( $HASH_BUILDER.ToString() -cne $env:SHA256_HASH ) {
|
||||
throw [System.Exception]::new("Created certificate hash $($HASH_BUILDER.ToString()) $(
|
||||
)doesn't match provided hash $($env:SHA256_HASH)")
|
||||
}
|
||||
env:
|
||||
BASE64_STR: ${{ secrets.PACKAGE_CERTIFICATE_BASE64 }}
|
||||
SHA256_HASH: ${{ secrets.PACKAGE_CERTIFICATE_SHA256 }}
|
||||
|
||||
- name: Restore the application
|
||||
id: restore_application
|
||||
shell: pwsh
|
||||
run: |
|
||||
msbuild $env:SOLUTION_NAME /t:Restore
|
||||
nuget restore $env:SOLUTION_NAME
|
||||
|
||||
- if: matrix.runCodeqlAnalysis
|
||||
name: Initialize CodeQL
|
||||
id: init_codeql
|
||||
uses: github/codeql-action/init@v3
|
||||
with:
|
||||
queries: security-and-quality
|
||||
languages: csharp
|
||||
|
||||
- name: Build and generate bundles
|
||||
id: build_app
|
||||
shell: pwsh
|
||||
run: |
|
||||
msbuild $env:SOLUTION_NAME `
|
||||
/p:Platform=$env:PLATFORM `
|
||||
/p:Configuration=$env:CONFIGURATION `
|
||||
/p:UapAppxPackageBuildMode=$env:UAP_APPX_PACKAGE_BUILD_MODE `
|
||||
/p:AppxBundle=$env:APPX_BUNDLE `
|
||||
/p:AppxPackageSigningEnabled=$env:APPX_PACKAGE_SIGNING_ENABLED `
|
||||
/p:AppxBundlePlatforms=$env:APPX_BUNDLE_PLATFORMS `
|
||||
/p:AppxPackageDir=$env:ARTIFACTS_DIR `
|
||||
/p:PackageCertificateKeyFile=$env:PACKAGE_CERTIFICATE_KEYFILE `
|
||||
/p:PackageCertificatePassword=$env:PACKAGE_CERTIFICATE_PASSWORD
|
||||
env:
|
||||
PLATFORM: x64
|
||||
UAP_APPX_PACKAGE_BUILD_MODE: StoreUpload
|
||||
APPX_BUNDLE: Always
|
||||
APPX_PACKAGE_SIGNING_ENABLED: ${{ matrix.newVersion != '' }}
|
||||
APPX_BUNDLE_PLATFORMS: ${{ matrix.appxBundlePlatforms }}
|
||||
ARTIFACTS_DIR: ${{ github.workspace }}\Artifacts
|
||||
PACKAGE_CERTIFICATE_KEYFILE: ${{ github.workspace }}\cert.pfx
|
||||
PACKAGE_CERTIFICATE_PASSWORD: ${{ secrets.PACKAGE_CERTIFICATE_PWD }}
|
||||
APP_CENTER_SECRET: ${{ secrets.APP_CENTER_SECRET }}
|
||||
|
||||
- if: matrix.debug && !contains( matrix.appxBundlePlatforms, 'arm64' )
|
||||
name: Test ARM build in debug configuration
|
||||
id: build_app_arm_debug
|
||||
shell: pwsh
|
||||
run: |
|
||||
msbuild $env:SOLUTION_NAME `
|
||||
/p:Platform=$env:PLATFORM `
|
||||
/p:Configuration=$env:CONFIGURATION `
|
||||
/p:UapAppxPackageBuildMode=$env:UAP_APPX_PACKAGE_BUILD_MODE `
|
||||
/p:AppxBundle=$env:APPX_BUNDLE `
|
||||
/p:AppxBundlePlatforms=$env:APPX_BUNDLE_PLATFORMS
|
||||
env:
|
||||
PLATFORM: ARM64
|
||||
UAP_APPX_PACKAGE_BUILD_MODE: StoreUpload
|
||||
APPX_BUNDLE: Always
|
||||
APPX_BUNDLE_PLATFORMS: ARM64
|
||||
|
||||
- if: matrix.runCodeqlAnalysis
|
||||
name: Perform CodeQL Analysis
|
||||
id: analyze_codeql
|
||||
uses: github/codeql-action/analyze@v3
|
||||
continue-on-error: true
|
||||
|
||||
- if: matrix.runSonarCloudScan
|
||||
name: Send SonarCloud results
|
||||
id: send_sonar_results
|
||||
shell: pwsh
|
||||
run: |
|
||||
.\.sonar\scanner\dotnet-sonarscanner end `
|
||||
/d:sonar.login="$env:SONAR_TOKEN"
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.SONAR_GITHUB_TOKEN }}
|
||||
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
|
||||
|
||||
- if: matrix.newVersion != ''
|
||||
name: Upload build artifacts
|
||||
id: upload_artifacts
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: Build artifacts
|
||||
path: Artifacts/
|
||||
|
||||
cd:
|
||||
# This job will execute when the workflow is triggered on a 'workflow_dispatch' event,
|
||||
# the target branch is 'master' and required parameter provided for release.
|
||||
if: needs.ci.outputs.new_version != ''
|
||||
needs: [ setup, ci ]
|
||||
runs-on: windows-latest
|
||||
env:
|
||||
OLD_VERSION: ${{ needs.ci.outputs.old_version }}
|
||||
NEW_VERSION: ${{ needs.ci.outputs.new_version }}
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
id: checkout_repo
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Download and extract MSIX package
|
||||
id: dl_package_artifact
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: Build artifacts
|
||||
path: Artifacts/
|
||||
|
||||
- name: Create deployment payload
|
||||
id: create_notepads_zip
|
||||
shell: pwsh
|
||||
run: |
|
||||
Get-ChildItem -Filter *Production* -Recurse | Rename-Item -NewName { $_.name -replace "_Production|_Test",'' }
|
||||
Compress-Archive -Path "Notepads_$($env:NEW_VERSION)\*" `
|
||||
-DestinationPath "Notepads_$($env:NEW_VERSION)\Notepads_$($env:NEW_VERSION)_x86_x64_ARM64.zip"
|
||||
working-directory: ./Artifacts
|
||||
|
||||
- name: Generate changelog
|
||||
id: generate_changlog
|
||||
uses: mrchief/universal-changelog-action@v1.3.2
|
||||
with:
|
||||
previousReleaseTagNameOrSha: v${{ env.OLD_VERSION }}
|
||||
nextReleaseTagName: v${{ env.NEW_VERSION }}
|
||||
nextReleaseName: v${{ env.NEW_VERSION }}
|
||||
configFilePath: .github/RELEASE_TEMPLATE/changelog_config.json
|
||||
|
||||
- name: Create and publish release
|
||||
id: create_release
|
||||
uses: ncipollo/release-action@v1.16.0
|
||||
with:
|
||||
allowUpdates: true
|
||||
replacesArtifacts: true
|
||||
tag: v${{ env.NEW_VERSION }}
|
||||
name: Notepads v${{ env.NEW_VERSION }}
|
||||
body: ${{ steps.generate_changlog.outputs.changelog }}
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
artifacts:
|
||||
Artifacts/Notepads_${{ env.NEW_VERSION }}/Notepads_${{ env.NEW_VERSION }}_x86_x64_ARM64.msixbundle
|
||||
Artifacts/Notepads_${{ env.NEW_VERSION }}/Notepads_${{ env.NEW_VERSION }}_x86_x64_ARM64.zip
|
||||
|
||||
# - name: Publish to Windows Store
|
||||
# id: publish_to_store
|
||||
# uses: isaacrlevin/windows-store-action@1.0
|
||||
# with:
|
||||
# tenant-id: ${{ secrets.AZURE_AD_TENANT_ID }}
|
||||
# client-id: ${{ secrets.AZURE_AD_APPLICATION_CLIENT_ID }}
|
||||
# client-secret: ${{ secrets.AZURE_AD_APPLICATION_SECRET }}
|
||||
# app-id: ${{ secrets.STORE_APP_ID }}
|
||||
# package-path: "${{ github.workspace }}/Artifacts/"
|
||||
|
||||
# Built with ❤ by [Pipeline Foundation](https://pipeline.foundation)
|
||||
@ -0,0 +1,355 @@
|
||||
## 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
|
||||
*.rsuser
|
||||
*.suo
|
||||
*.user
|
||||
*.userosscache
|
||||
*.sln.docstates
|
||||
|
||||
# User-specific files (MonoDevelop/Xamarin Studio)
|
||||
*.userprefs
|
||||
|
||||
# Build results
|
||||
[Dd]ebug/
|
||||
[Dd]ebugPublic/
|
||||
[Rr]elease/
|
||||
[Rr]eleases/
|
||||
x64/
|
||||
x86/
|
||||
[Aa][Rr][Mm]/
|
||||
[Aa][Rr][Mm]64/
|
||||
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/
|
||||
|
||||
# StyleCop
|
||||
StyleCopReport.xml
|
||||
|
||||
# Files built by Visual Studio
|
||||
*_i.c
|
||||
*_p.c
|
||||
*_h.h
|
||||
*.ilk
|
||||
*.meta
|
||||
*.obj
|
||||
*.iobj
|
||||
*.pch
|
||||
*.pdb
|
||||
*.ipdb
|
||||
*.pgc
|
||||
*.pgd
|
||||
*.rsp
|
||||
*.sbr
|
||||
*.tlb
|
||||
*.tli
|
||||
*.tlh
|
||||
*.tmp
|
||||
*.tmp_proj
|
||||
*_wpftmp.csproj
|
||||
*.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/
|
||||
_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/
|
||||
*.rptproj.bak
|
||||
|
||||
# SQL Server files
|
||||
*.mdf
|
||||
*.ldf
|
||||
*.ndf
|
||||
|
||||
# Business Intelligence projects
|
||||
*.rdl.data
|
||||
*.bim.layout
|
||||
*.bim_*.settings
|
||||
*.rptproj.rsuser
|
||||
*- Backup*.rdl
|
||||
|
||||
# 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 personal settings
|
||||
.cr/personal
|
||||
|
||||
# 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
|
||||
|
||||
# MFractors (Xamarin productivity tool) working folder
|
||||
.mfractor/
|
||||
|
||||
# Local History for Visual Studio
|
||||
.localhistory/
|
||||
|
||||
# BeatPulse healthcheck temp database
|
||||
healthchecksdb
|
||||
|
||||
# Logs and databases #
|
||||
######################
|
||||
*.log
|
||||
*.sql
|
||||
*.sqlite
|
||||
|
||||
# OS generated files #
|
||||
######################
|
||||
.DS_Store
|
||||
.DS_Store?
|
||||
._*
|
||||
.Spotlight-V100
|
||||
.Trashes
|
||||
ehthumbs.db
|
||||
Thumbs.db
|
||||
@ -0,0 +1,8 @@
|
||||
{
|
||||
"checkRunSettings": {
|
||||
"vulnerableCheckRunConclusionLevel": "failure"
|
||||
},
|
||||
"issueSettings": {
|
||||
"minSeverityLevel": "LOW"
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,419 @@
|
||||
# Notepads CI/CD documentation
|
||||
|
||||
- after merging the PR, the first run of the "Notepads CI/CD Pipeline" workflow will not complete successfully, because it requires specific setup explained in this documentation. The two other workflows "CodeQL Analysis" and "Build", should complete successfully.
|
||||
|
||||
## 1. Set up SonarCloud
|
||||
|
||||
### SonarCloud is a cloud-based code quality and security service
|
||||
|
||||
#### Create SonarCloud project
|
||||
|
||||
- Go to https://sonarcloud.io/
|
||||
|
||||
- Click the "Log in" button and create a new account or connect with GitHub account (recommended)
|
||||
|
||||
- At the top right corner click the "+" sign
|
||||
|
||||
- From the dropdown select "Create new Organization"
|
||||
|
||||
- Click the "Choose an organization on GitHub" button
|
||||
|
||||
- Select an account for the organization setup
|
||||
|
||||
- On Repository Access select "Only select repositories" and select the project and click the "Save" button
|
||||
|
||||
- On the "Create organization page" don't change the Key and click "Continue"
|
||||
|
||||
- Select the Free plan then click the "Create Organization" button to finalize the creation of the Organization
|
||||
|
||||
#### Configure SonarCloud project
|
||||
|
||||
- At the top right corner click the "+" sign and select "Analyze new project"
|
||||
|
||||
- Select the project and click the "Set Up" button in the box on the right
|
||||
|
||||
- Under "Choose your analysis method" click "With GitHub Actions" and **keep the following page open**
|
||||
|
||||
- [Create a new PAT with **repo_deployment** and **read:packages** permissions](#7-how-to-create-a-pat) and copy the value of the generated token
|
||||
|
||||
- In the project's GitHub repository, go to the **Settings** tab -> Secrets
|
||||
|
||||
- Click on **New Repository secret** and create a new secret with the name **SONAR_GITHUB_TOKEN** and the token you just copied as the value
|
||||
|
||||
- Create another secret with the two values from the SonarCloud page you kept open, which you can close after completing this step
|
||||
|
||||

|
||||
|
||||
- [Run the "Notepads CI/CD Pipeline" workflow manually](#2-run-workflow-manually)
|
||||
|
||||
#### Set Quality Gate
|
||||
|
||||
- After the "Notepads CI/CD Pipeline" workflow has executed successfully, go to https://sonarcloud.io/projects and click on the project
|
||||
|
||||
- In the alert bar above the results, click the "Set new code definition" button and select "Previous version" (notice the "New Code definition has been updated" alert at the top)
|
||||
|
||||
- The Quality Gate will become active as soon as the next SonarCloud scan completes successfully
|
||||
|
||||
<br>
|
||||
<a name="workflow_dispatch"></a>
|
||||
|
||||
## 2. Run workflow manually
|
||||
|
||||
Once you've set up all the steps above correctly, you should be able to successfully complete a manual execution of the "Notepads CI/CD Pipeline" workflow.
|
||||
|
||||
1. Go to the project's GitHub repository and click on the **Actions** tab
|
||||
|
||||
2. From the "Workflows" list on the left, click on "Notepads CI/CD Pipeline"
|
||||
|
||||
3. On the right, next to the "This workflow has a workflow_dispatch event trigger" label, click on the "Run workflow" dropdown, make sure the default branch is selected (if not manually changed, should be main or master) in the "Use workflow from" dropdown and click the "Run workflow" button
|
||||
|
||||
4. You can optionally fill the argument textbox with "release" to trigger [GitHub Release](#github_release) and [Store Upload](#store_upload)
|
||||
|
||||

|
||||
|
||||
5. Once the workflow run has completed successfully, move on to the next step of the documentation
|
||||
|
||||
NOTE: **screenshots are only exemplary**
|
||||
|
||||
<br>
|
||||
|
||||
## 3. Set up Dependabot
|
||||
|
||||
Dependabot is a GitHub native security tool that goes through the dependencies in the project and creates alerts, and PRs with updates when a new and/or non-vulnerable version is found.
|
||||
|
||||
- for PRs with version updates, this pipeline comes pre-configured for all current dependency sources in the project, so at "Insights" tab -> "Dependency graph" -> "Dependabot", you should be able to see all tracked sources of dependencies, when they have been checked last and view a full log of the last check
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
### Set up security alerts and updates
|
||||
|
||||
##### - GitHub, through Dependabot, also natively offers a security check for vulnerable dependencies
|
||||
|
||||
1. Go to "Settings" tab of the repo
|
||||
|
||||
2. Go to "Security & analysis" section
|
||||
|
||||
3. Click "Enable" for both "Dependabot alerts" and "Dependabot security updates"
|
||||
|
||||
- By enabling "Dependabot alerts", you would be notified for any vulnerable dependencies in the project. At "Security" tab -> "Dependabot alerts", you can manage all alerts. By clicking on an alert, you would be able to see a detailed explanation of the vulnerability and a viable solution.
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
- By enabling "Dependabot security updates", you authorize Dependabot to create PRs specifically for **security updates**
|
||||
|
||||

|
||||
|
||||
### Set up Dependency graph
|
||||
|
||||
##### - The "Dependency graph" option should be enabled by default for all public repos, but in case it isn't:
|
||||
|
||||
1. Go to "Settings" tab of the repo
|
||||
|
||||
2. Go to "Security&Analysis" section
|
||||
|
||||
3. Click "Enable" for the "Dependency graph" option
|
||||
|
||||
- this option enables the "Insights" tab -> "Dependency graph" section -> "Dependencies" tab, in which all the dependencies for the project are listed, under the different manifests they are included in
|
||||
|
||||

|
||||
|
||||
NOTE: **screenshots are only exemplary**
|
||||
|
||||
<br>
|
||||
|
||||
## 4. CodeQL
|
||||
|
||||
CodeQL is GitHub's own industry-leading semantic code analysis engine. CodeQL requires no setup, because it comes fully pre-configured by us.
|
||||
|
||||
To activate it and see its results, only a push commit or a merge of a PR to the default branch of the repository, is required.
|
||||
|
||||
We've also configured CodeQL to run on schedule, so every day at 8:00AM UTC, it automatically scans the code.
|
||||
|
||||
- you can see the results here at **Security** tab -> **Code scanning alerts** -> **CodeQL**:
|
||||
|
||||

|
||||
|
||||
- on the page of each result, you can see an explanation of what the problem is and also one or more solutions:
|
||||
|
||||

|
||||
|
||||
### Code scanning alerts bulk dismissal tool
|
||||
|
||||
##### - currently, GitHub allows for only 25 code scanning alerts to be dismissed at a time. Sometimes, you might have hundreds you would like to dismiss, so you will have to click many times and wait for a long time to dismiss them. Via the "csa-bulk-dismissal.yml", you can automatically dismiss unnecessary alerts or manually do that with one click.
|
||||
|
||||
NOTE: This tool executes automatically when **Notepads CI/CD Pipeline** action completes.
|
||||
|
||||
#### 1. Setup
|
||||
|
||||
1. In the repository, go to the **Settings** tab -> **Secrets**
|
||||
|
||||

|
||||
|
||||
2. Add the following secrets with the name and the corresponding value, by at the upper right of the section, clicking on the **New repository secret** button:
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
- CSA_ACCESS_TOKEN - [create a PAT with "security_events" permission only](#7-how-to-create-a-pat).
|
||||
|
||||
#### 2. Execution
|
||||
|
||||
1. This tool is automatically triggered when **Notepads CI/CD Pipeline** task completes, if you want to manually execute this follow next steps
|
||||
|
||||
2. In your repo, click on the Actions tab and on the left, in the Workflows list, click on the "Code scanning alerts bulk dismissal"
|
||||
|
||||

|
||||
|
||||
3. On the right, click on the "Run workflow" dropdown. Under "Use workflow from" choose your default branch (usually main/master), in the **Type of filter to use** field type "path"/"desc" depending upon whether dismiss alerts based on predefined paths or description respectively (default is "path"), in the **Reason for dismissal** type "fp"/"wf"/"ut" for "false positive"/"won't fix"/"used in tests" respectively (default is "wf") and click on the **Run workflow** button
|
||||
|
||||
<a name="csa_execute"></a>
|
||||

|
||||
|
||||
NOTE: if any unsupported values are entered default values will be used
|
||||
|
||||
4. If everything was set up currently in the "Setup" phase, the "Code scanning alerts bulk dismissal" workflow is going to be executed successfully, which after some time, would result in **all** previously open code scanning alerts, with a certain description be dismissed
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
NOTE: "closed" refers to "dismissed" alerts
|
||||
|
||||
#### 3. Customization
|
||||
|
||||
The "setup" job in the pipeline, allows for more precise filtering of alerts to bulk dismiss. It uses the filter type to choose (filter based on path or description) from the alert to determine if it has to be dismissed or not. We've added the following paths and alert descriptions by default:
|
||||
|
||||
##### Paths:
|
||||
|
||||
- "\*/obj/\*" (if path contains `obj` folder at any position)
|
||||
|
||||
##### Descriptions:
|
||||
|
||||
- "Calls to unmanaged code"
|
||||
- "Unmanaged code"
|
||||
|
||||
##### To add more paths, follow these steps:
|
||||
|
||||
1. In your source code, open ".github/workflows/csa-bulk-dismissal.yml"
|
||||
|
||||
2. From line 50 to 56, notice "$MATRIX = **". This is the [powershell hashtable](https://docs.microsoft.com/powershell/scripting/learn/deep-dives/everything-about-hashtable?view=powershell-7.1) of filters that the CSABD (Code scanning alerts bulk dismissal) tool uses to filter through the alerts:
|
||||
|
||||

|
||||
|
||||
3. To add more paths under **include** element use comma separation and followed from next line add `@{ filter = "New path" }`. Replace "New path" with the path (with or without [wild cards](https://docs.microsoft.com/powershell/module/microsoft.powershell.core/about/about_wildcards?view=powershell-7.1)) you want:
|
||||
|
||||

|
||||
|
||||
##### To add more descriptions, follow these steps:
|
||||
|
||||
1. In your source code, open ".github/workflows/csa-bulk-dismissal.yml"
|
||||
|
||||
2. From line 58 to 67, notice "$MATRIX = **". This is the [powershell hashtable](https://docs.microsoft.com/powershell/scripting/learn/deep-dives/everything-about-hashtable?view=powershell-7.1) of filters that the CSABD (Code scanning alerts bulk dismissal) tool uses to filter through the alerts:
|
||||
|
||||

|
||||
|
||||
3. To add more descriptions under **include** element use comma separation and followed from next line add `@{ filter = "New description" }`. Replace "New description" with the description you want:
|
||||
|
||||

|
||||
|
||||
##### To change default filter type and dismissal reason, follow these steps:
|
||||
|
||||
1. In your source code, open ".github/workflows/csa-bulk-dismissal.yml"
|
||||
|
||||
2. To change default filter type change **$FILTER_TYPE** variable in line 31 to something else (default is "path", supported are: "desc" and "path"):
|
||||
|
||||

|
||||
|
||||
3. To change dismissal reason change **$REASON** variable in line 45 to something else (default is "won't fix", supported are: "false positive", "won't fix" and "used in tests"):
|
||||
|
||||

|
||||
|
||||
NOTE: changing default filter type and dismissal reason won't change dafault value typed when [manually executing](#csa_execute) tool, change values in line 13 and 17 respectively to reflect the change
|
||||
|
||||

|
||||
|
||||
<br>
|
||||
<a name="github_release"></a>
|
||||
|
||||
## 5. Automated GitHub release
|
||||
|
||||
When triggered bumps up the GitHub tag in the repo and executes the CD job and produces release with changelogs
|
||||
|
||||
Note: **not every commit to your master branch are included in changelog**
|
||||
|
||||
#### Setup
|
||||
|
||||
Add the following secrets by going to the repo **Settings** tab -> **Secrets**:
|
||||
|
||||
1. **PACKAGE_CERTIFICATE_BASE64**
|
||||
|
||||
- used to dynamically create the PFX file required for the signing of the **msixbundle**
|
||||
- use the following PowerShell code locally to turn your PFX file into Base64:
|
||||
|
||||
```
|
||||
# read from PFX as binary
|
||||
$PFX_FILE = [IO.File]::ReadAllBytes('absolute_path_to_PFX')
|
||||
# convert to Base64 and write in txt
|
||||
[System.Convert]::ToBase64String($PFX_FILE) | Out-File 'absolute_path\cert.txt'
|
||||
```
|
||||
|
||||
- copy the contents of the **cert.txt** and paste as the value of the secret
|
||||
|
||||
2. **PACKAGE_CERTIFICATE_PWD**
|
||||
|
||||
- used in the build of the project to authenticate the PFX
|
||||
- copy and paste the password of your PFX as the value of this secret
|
||||
|
||||
NOTE:
|
||||
|
||||
- none of those values are visible in the logs of the pipeline, nor are available to anyone outside of the original repository e.g. forks, anonymous clones etc.
|
||||
- the dynamically created PFX file lives only for the duration of the pipeline execution
|
||||
|
||||
#### Execution
|
||||
|
||||
[Once you've set up all the steps for manual execution of the "Notepads CI/CD Pipeline" workflow correctly](#workflow_dispatch), you should be able to successfully trigger release with the same workflow.
|
||||
|
||||
1. Go to the project's GitHub repository and click on the **Actions** tab
|
||||
|
||||
2. From the "Workflows" list on the left, click on "Notepads CI/CD Pipeline"
|
||||
|
||||
3. On the right, next to the "This workflow has a workflow_dispatch event trigger" label, click on the "Run workflow" dropdown, make sure the default branch is selected (if not manually changed, should be main or master) in the "Use workflow from" dropdown, type "release" in the argument textbox (By default "test" is typed) and click the "Run workflow" button
|
||||
|
||||

|
||||
|
||||
4. The workflow will produce release assets and calculate version, generate changelogs from valid commits since previous tag.
|
||||
|
||||
NOTE: **screenshots are only exemplary**
|
||||
|
||||
<br>
|
||||
|
||||
#### - follow these instructions for any commit (push or PR merge) to your master branch, you would like to see in changelog and count towards version change.
|
||||
|
||||
You would need one of three keywords at the start of your commit title. Each of the three keywords corresponds to a number in your release version i.e. v1.2.3. The release versioning uses the ["Conventional Commits" specification](https://www.conventionalcommits.org/en/v1.0.0/):
|
||||
|
||||
- "fix: ..." - this keyword corresponds to the last number v1.2.**3**, also known as PATCH;
|
||||
- "feat: ..." - this keyword corresponds to the middle number v1.**2**.3, also known as MINOR;
|
||||
- "perf: ..." - this keyword corresponds to the first number v**1**.2.3, also known as MAJOR. In addition, to trigger a MAJOR release, you would need to write "BREAKING CHANGE: ..." in the description of the commit, with an empty line above it to indicate it is in the <footer> portion of the description;
|
||||
|
||||
Note: when making a MAJOR release by committing through a terminal, use the multiple line syntax to add the commit title on one line and then adding an empty line, and then adding the "BREAKING CHANGE: " label
|
||||
<br><br>
|
||||
|
||||
#### Examples
|
||||
|
||||
Example(fix/PATCH): <br>
|
||||
`git commit -a -m "fix: this is a PATCH release triggering commit"`
|
||||
<br>
|
||||
`git push origin master`
|
||||
<br>
|
||||
<br>
|
||||
On triggering `Release`:
|
||||
<br>
|
||||
Result: v1.2.3 -> **v1.2.4**
|
||||
<br>
|
||||
<br>
|
||||
<br>
|
||||
Example(feat/MINOR): <br>
|
||||
`git commit -a -m "feat: this is a MINOR release triggering commit"`
|
||||
<br>
|
||||
`git push origin master`
|
||||
<br>
|
||||
<br>
|
||||
On triggering `Release`:
|
||||
<br>
|
||||
Result: v1.2.3 -> **v1.3.0**
|
||||
<br>
|
||||
<br>
|
||||
<br>
|
||||
Example(perf/MAJOR): <br>
|
||||
`` git commit -a -m "perf: this is a MAJOR release triggering commit ` ``
|
||||
<br>
|
||||
>> <br>
|
||||
>> `BREAKING CHANGE: this is the breaking change"`
|
||||
<br>
|
||||
`git push origin master`
|
||||
<br>
|
||||
<br>
|
||||
On triggering `Release`:
|
||||
<br>
|
||||
Result: v1.2.3 -> **v2.0.0**
|
||||
<br>
|
||||
<br>
|
||||
Note: in the MAJOR release example, the PowerShell multiline syntax ` (backtick) is used. After writing a backtick, a press of the Enter key should open a new line.
|
||||
|
||||
<br>
|
||||
<a name="store_upload"></a>
|
||||
|
||||
## 6. Setup automated publishing to the Windows Store
|
||||
|
||||
#### - for the automation to work, at least one submission needs to be already created manually
|
||||
|
||||
- [Create an Azure AD tenant](https://docs.microsoft.com/en-us/azure/active-directory/develop/quickstart-create-new-tenant) or use an existing one
|
||||
|
||||
- Associate your [Microsoft Partner Center with the Azure AD tenant](https://docs.microsoft.com/en-us/windows/uwp/publish/associate-azure-ad-with-partner-center)
|
||||
|
||||
- [Create a new app registration](https://docs.microsoft.com/en-us/azure/devops/pipelines/library/service-endpoints?view=azure-devops&tabs=yaml) or use an existing one from the list in your **portal.azure.com** -> **Azure Active Directory** -> **App registrations** section
|
||||
|
||||
- Add the [Azure AD application to the Microsoft Partner Center](https://docs.microsoft.com/en-us/partner-center/service-principal) and give it "Manager" permissions
|
||||
|
||||
- In the project's GitHub repo, create the following secrets:
|
||||
|
||||
1. **AZURE_AD_TENANT_ID** and **AZURE_AD_APPLICATION_CLIENT_ID**
|
||||
|
||||
- copy and paste the values shown in the screenshot below to the appropriate secret:
|
||||
|
||||

|
||||
|
||||
Note: screenshot is taken from **portal.azure.com** -> **Azure AD** -> **App registrations** -> **app-name** page
|
||||
|
||||
2. **AZURE_AD_APPLICATION_SECRET**
|
||||
|
||||
- copy and paste the value you get on the page following from **Account settings** -> **User management** -> **Azure AD applications** -> click on the added application:
|
||||
|
||||

|
||||
|
||||
3. **STORE_APP_ID**
|
||||
|
||||
- copy and paste the highlighted code as the value of this secret:
|
||||
|
||||

|
||||
|
||||
- If everything was setup correctly, on your next push commit to the `master` branch with a new `Identity.Version` in the `Package.appxmanifest`, a new submission in the Microsoft Partner Center with the new `*.msixupload` package should appear and be automatically submitted if all verifications pass
|
||||
|
||||
<br>
|
||||
|
||||
## 7. How to create a PAT
|
||||
|
||||
- In a new tab open GitHub, at the top right corner, click on your profile picture and click on **Settings** from the dropdown.
|
||||
|
||||

|
||||
|
||||
- Go to Developer Settings -> Personal access tokens.
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
- Click the **Generate new token** button and enter password if prompted.
|
||||
|
||||

|
||||
|
||||
- Name the token, from the permissions list choose the ones needed and at the bottom click on the **Generate token** button.
|
||||
|
||||

|
||||
|
||||
- Copy the token value and paste it wherever its needed
|
||||
|
||||

|
||||
|
||||
NOTE: once you close or refresh the page, you won't be able to copy the value of the PAT again!
|
||||
|
||||
#
|
||||
|
||||
Built with ❤ by [Pipeline Foundation](https://pipeline.foundation)
|
||||
@ -0,0 +1,76 @@
|
||||
# Contributor Covenant Code of Conduct
|
||||
|
||||
## Our Pledge
|
||||
|
||||
In the interest of fostering an open and welcoming environment, we as
|
||||
contributors and maintainers pledge to making participation in our project and
|
||||
our community a harassment-free experience for everyone, regardless of age, body
|
||||
size, disability, ethnicity, sex characteristics, gender identity and expression,
|
||||
level of experience, education, socio-economic status, nationality, personal
|
||||
appearance, race, religion, or sexual identity and orientation.
|
||||
|
||||
## Our Standards
|
||||
|
||||
Examples of behavior that contributes to creating a positive environment
|
||||
include:
|
||||
|
||||
* Using welcoming and inclusive language
|
||||
* Being respectful of differing viewpoints and experiences
|
||||
* Gracefully accepting constructive criticism
|
||||
* Focusing on what is best for the community
|
||||
* Showing empathy towards other community members
|
||||
|
||||
Examples of unacceptable behavior by participants include:
|
||||
|
||||
* The use of sexualized language or imagery and unwelcome sexual attention or
|
||||
advances
|
||||
* Trolling, insulting/derogatory comments, and personal or political attacks
|
||||
* Public or private harassment
|
||||
* Publishing others' private information, such as a physical or electronic
|
||||
address, without explicit permission
|
||||
* Other conduct which could reasonably be considered inappropriate in a
|
||||
professional setting
|
||||
|
||||
## Our Responsibilities
|
||||
|
||||
Project maintainers are responsible for clarifying the standards of acceptable
|
||||
behavior and are expected to take appropriate and fair corrective action in
|
||||
response to any instances of unacceptable behavior.
|
||||
|
||||
Project maintainers have the right and responsibility to remove, edit, or
|
||||
reject comments, commits, code, wiki edits, issues, and other contributions
|
||||
that are not aligned to this Code of Conduct, or to ban temporarily or
|
||||
permanently any contributor for other behaviors that they deem inappropriate,
|
||||
threatening, offensive, or harmful.
|
||||
|
||||
## Scope
|
||||
|
||||
This Code of Conduct applies both within project spaces and in public spaces
|
||||
when an individual is representing the project or its community. Examples of
|
||||
representing a project or community include using an official project e-mail
|
||||
address, posting via an official social media account, or acting as an appointed
|
||||
representative at an online or offline event. Representation of a project may be
|
||||
further defined and clarified by project maintainers.
|
||||
|
||||
## Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||
reported by contacting the project team at NotepadsApp@outlook.com. All
|
||||
complaints will be reviewed and investigated and will result in a response that
|
||||
is deemed necessary and appropriate to the circumstances. The project team is
|
||||
obligated to maintain confidentiality with regard to the reporter of an incident.
|
||||
Further details of specific enforcement policies may be posted separately.
|
||||
|
||||
Project maintainers who do not follow or enforce the Code of Conduct in good
|
||||
faith may face temporary or permanent repercussions as determined by other
|
||||
members of the project's leadership.
|
||||
|
||||
## Attribution
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
|
||||
available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
|
||||
|
||||
[homepage]: https://www.contributor-covenant.org
|
||||
|
||||
For answers to common questions about this code of conduct, see
|
||||
https://www.contributor-covenant.org/faq
|
||||
@ -0,0 +1,35 @@
|
||||
# How to Contribute:
|
||||
|
||||
You can contribute to Notepads project by:
|
||||
- Report issues and bugs [here](https://github.com/0x7c13/Notepads/issues)
|
||||
- Submit feature requests [here](https://github.com/0x7c13/Notepads/issues)
|
||||
- Create a pull request to help me (Let me know before you do so):
|
||||
* Fix an existing bug, prefix title with `fix: `.
|
||||
* Implement new features, prefix title with `feat: `.
|
||||
* Fix grammar errors or improve my documentations, prefix title with `doc: `.
|
||||
* Improve CI/CD pipeline, prefix title with `ci: `.
|
||||
* Cleanup code and code refactoring or anything else you want to change in the project not listed above, prefix title with `other: ` or assign a custom prefix with the same format (`label: `).
|
||||
- Internationalization and localization:
|
||||
* My only inputs for the work here is to recommend you guys to use existing phrases that you found in win32 notepad.exe or vs code or notepad++ as much as possible. It makes your translations more consistent and easier to understand by end users.
|
||||
* Since Notepads is still in early beta. I might change texts and add texts now and then for the upcoming months. Whenever that happens, I will notify you in [Notepads Discord Server](https://discord.gg/VqetCub) (Please join it if possible) and in [GitHub Discussions](https://github.com/0x7c13/Notepads/discussions/818) (Subscribe to notifications). If someday you lose the passion, feel free to let me know so I can assign your language to others.
|
||||
* OK, here are the steps you need to follow if you want to contribute:
|
||||
1. Make sure you can build and run Notepads project on your machine so that you can test it after your work.
|
||||
2. Click [here](https://github.com/0x7c13/Notepads/discussions/818) and provide your information.
|
||||
3. Do your work and test it on your machine and check your work to make sure it is not breaking any existing layout.
|
||||
4. Finish your work and create a PR, prefix PR title with `lang: ` (Example: https://github.com/0x7c13/Notepads/pull/30)
|
||||
5. Let me know and I will merge it if it looks good to me.
|
||||
Notes: You should use the language code as your folder name listed here: https://docs.microsoft.com/en-us/windows/uwp/publish/supported-languages
|
||||
|
||||
Note: This repository follows [conventional commits](https://www.conventionalcommits.org/en/v1.0.0/), format your pull request title according to specifications.
|
||||
|
||||
# How to Build and Run Notepads from source:
|
||||
* Make sure your machine is running on Windows 10 1903+.
|
||||
* Make sure you have Visual Studio 2019 16.2 or newer installed.
|
||||
* Make sure you have "Universal Windows Platform development" component installed for Visual Studio.
|
||||
* Make sure you installed "Windows 10 SDK (10.0.17763.0 + 10.0.19041.0)" as well.
|
||||
* Open src/Notepads.sln with Visual Studio and set Solution Platform to x64(amd64).
|
||||
* Once opened, right click on the solution and click on "Restore NuGet Packages".
|
||||
* Now you should be able to build and run Notepads on your machine. If it fails, try close the solution and reopen it again.
|
||||
|
||||
# TL;DR:
|
||||
This is my first UWP project and I learn as I go. As a result, the code base is not well organized, and it is not well written. The philosophy here is to create a text editor that is easy to use, lightweight and yet stylish instead of creating another Notepad++ or VS Code in anyway. If you are looking for a code/programming editor, you might want to use VS Code instead. If you are looking for a lightweight text editor, you come to the right place. Notepads is here to help you do small things quicker and you should always install and use other editors that suit your need.
|
||||
@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2019-2024 Jackie (Jiaqi) Liu
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@ -1 +0,0 @@
|
||||
Subproject commit 39a2159ca24d2c04889b651c41889c72a7a9db43
|
||||
@ -0,0 +1,24 @@
|
||||
Privacy Policy
|
||||
----------------
|
||||
|
||||
### Introduction
|
||||
Our privacy policy will help you understand what information we collect by *[Notepads](https://github.com/0x7c13/Notepads)* app, how *[Notepads](https://github.com/0x7c13/Notepads)* app uses it, and what choices you have.
|
||||
*[0x7c13](https://github.com/0x7c13)* with the help of the Github Notepads App community built the *[Notepads](https://github.com/0x7c13/Notepads)* app as a free app. This APP is provided by *[0x7c13](https://github.com/0x7c13)* at no cost and is intended for use as is.
|
||||
If you choose to use this app, then you agree to the collection and use of information in relation with this policy. The data that we collect are used for providing and improving the app service. We will not use or share your information or usage data with anyone except as described in this Privacy Policy.
|
||||
|
||||
### Information Collection and Use
|
||||
For a better experience while using this app, certain usage data and errors are collected for identifying issues or improving the user experience of the app. *[Visual Studio AppCenter](https://visualstudio.microsoft.com/app-center/)* analytics service is used in this app to collect basic usage data plus some minimum telemetry to help debug runtime errors. See thread [#334](https://github.com/0x7c13/Notepads/issues/334) for more details.
|
||||
The app does NOT use third party services that may collect information used to identify you.
|
||||
|
||||
Note: Visual Studio App Center is scheduled for retirement on March 31, 2025. Notepads v1.5.6.0+ starts to use Microsoft Store Services SDK to log non-privacy usage data and errors.
|
||||
|
||||
### Data Security
|
||||
We value your trust in providing us your Personal Information, thus we are striving to use commercially acceptable means of protecting it. But remember that no method of transmission over the internet, or method of electronic storage is 100% secure and reliable, and we cannot guarantee its absolute security.
|
||||
|
||||
### Changes to This Privacy Policy
|
||||
We may update our Privacy Policy from time to time. Thus, you are advised to review this page periodically for any changes. We will notify you of any changes by posting the new Privacy Policy on this page. These changes are effective immediately, after they are posted on this page.
|
||||
|
||||
### Contact Us
|
||||
If you have any questions or suggestions about our Privacy Policy, do not hesitate to contact us.
|
||||
Contact Information:
|
||||
Email: notepadsapp@outlook.com
|
||||
|
After Width: | Height: | Size: 313 KiB |
|
After Width: | Height: | Size: 256 KiB |
|
After Width: | Height: | Size: 233 KiB |
|
After Width: | Height: | Size: 305 KiB |
|
After Width: | Height: | Size: 136 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 6.7 KiB |
|
After Width: | Height: | Size: 7.3 KiB |
|
After Width: | Height: | Size: 4.6 KiB |
|
After Width: | Height: | Size: 5.9 KiB |
|
After Width: | Height: | Size: 10 KiB |
|
After Width: | Height: | Size: 18 KiB |
|
After Width: | Height: | Size: 19 KiB |
|
After Width: | Height: | Size: 101 KiB |
|
After Width: | Height: | Size: 18 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 29 KiB |
|
After Width: | Height: | Size: 24 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 3.9 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 21 KiB |
|
After Width: | Height: | Size: 5.5 KiB |
|
After Width: | Height: | Size: 10 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 47 KiB |
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 1.7 KiB |
|
After Width: | Height: | Size: 44 KiB |
|
After Width: | Height: | Size: 46 KiB |
|
After Width: | Height: | Size: 56 KiB |
|
After Width: | Height: | Size: 40 KiB |
|
After Width: | Height: | Size: 21 KiB |
|
After Width: | Height: | Size: 45 KiB |
|
After Width: | Height: | Size: 39 KiB |
|
After Width: | Height: | Size: 36 KiB |
|
After Width: | Height: | Size: 21 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 2.0 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 18 KiB |
|
After Width: | Height: | Size: 8.9 KiB |
@ -0,0 +1,39 @@
|
||||
# Universal Windows Platform build definition
|
||||
|
||||
trigger:
|
||||
paths:
|
||||
exclude:
|
||||
- '*.md'
|
||||
- 'ScreenShots/'
|
||||
- '.whitesource'
|
||||
- '.github/'
|
||||
tags:
|
||||
exclude:
|
||||
- '*'
|
||||
|
||||
pool:
|
||||
vmImage: 'windows-latest'
|
||||
|
||||
variables:
|
||||
solution: '**/*.sln'
|
||||
buildPlatform: 'x86|x64|arm64'
|
||||
buildConfiguration: 'Production'
|
||||
appxPackageDir: '$(build.artifactStagingDirectory)\AppxPackages\\'
|
||||
|
||||
steps:
|
||||
- task: NuGetToolInstaller@1
|
||||
|
||||
- task: NuGetCommand@2
|
||||
inputs:
|
||||
restoreSolution: '$(solution)'
|
||||
|
||||
- task: VSBuild@1
|
||||
inputs:
|
||||
platform: 'x64'
|
||||
solution: '$(solution)'
|
||||
configuration: '$(buildConfiguration)'
|
||||
msbuildArgs: '/p:AppxBundlePlatforms="$(buildPlatform)"
|
||||
/p:AppxPackageDir="$(appxPackageDir)"
|
||||
/p:AppxBundle=Always
|
||||
/p:UapAppxPackageBuildMode=StoreUpload
|
||||
/p:AppxPackageSigningEnabled=false'
|
||||
@ -0,0 +1,323 @@
|
||||
# Remove the line below if you want to inherit .editorconfig settings from higher directories
|
||||
root = true
|
||||
|
||||
# C# files
|
||||
[*.cs]
|
||||
|
||||
#### Core EditorConfig Options ####
|
||||
|
||||
# Indentation and spacing
|
||||
indent_size = 4
|
||||
indent_style = space
|
||||
tab_width = 4
|
||||
|
||||
# New line preferences
|
||||
end_of_line = crlf
|
||||
insert_final_newline = false
|
||||
|
||||
#### .NET Coding Conventions ####
|
||||
|
||||
# this. and Me. preferences
|
||||
dotnet_style_qualification_for_event = false:silent
|
||||
dotnet_style_qualification_for_field = false:silent
|
||||
dotnet_style_qualification_for_method = false:silent
|
||||
dotnet_style_qualification_for_property = false:silent
|
||||
|
||||
# Language keywords vs BCL types preferences
|
||||
dotnet_style_predefined_type_for_locals_parameters_members = true:silent
|
||||
dotnet_style_predefined_type_for_member_access = true:silent
|
||||
|
||||
# Parentheses preferences
|
||||
dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity:silent
|
||||
dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:silent
|
||||
dotnet_style_parentheses_in_other_operators = never_if_unnecessary:silent
|
||||
dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity:silent
|
||||
|
||||
# Modifier preferences
|
||||
dotnet_style_require_accessibility_modifiers = for_non_interface_members:silent
|
||||
|
||||
# Expression-level preferences
|
||||
csharp_style_deconstructed_variable_declaration = true:suggestion
|
||||
csharp_style_inlined_variable_declaration = true:silent
|
||||
csharp_style_throw_expression = true:suggestion
|
||||
dotnet_style_coalesce_expression = true:suggestion
|
||||
dotnet_style_collection_initializer = true:suggestion
|
||||
dotnet_style_explicit_tuple_names = true:suggestion
|
||||
dotnet_style_null_propagation = true:suggestion
|
||||
dotnet_style_object_initializer = true:suggestion
|
||||
dotnet_style_prefer_auto_properties = true:silent
|
||||
dotnet_style_prefer_conditional_expression_over_assignment = true:silent
|
||||
dotnet_style_prefer_conditional_expression_over_return = true:silent
|
||||
dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion
|
||||
dotnet_style_prefer_inferred_tuple_names = true:suggestion
|
||||
dotnet_style_prefer_is_null_check_over_reference_equality_method = true:suggestion
|
||||
|
||||
# Field preferences
|
||||
dotnet_style_readonly_field = true:suggestion
|
||||
|
||||
#### C# Coding Conventions ####
|
||||
|
||||
# var preferences
|
||||
csharp_style_var_elsewhere = true:silent
|
||||
csharp_style_var_for_built_in_types = true:silent
|
||||
csharp_style_var_when_type_is_apparent = true:silent
|
||||
|
||||
# Expression-bodied members
|
||||
csharp_style_expression_bodied_accessors = true:silent
|
||||
csharp_style_expression_bodied_constructors = false:silent
|
||||
csharp_style_expression_bodied_indexers = true:silent
|
||||
csharp_style_expression_bodied_lambdas = true:silent
|
||||
csharp_style_expression_bodied_methods = false:silent
|
||||
csharp_style_expression_bodied_operators = true:silent
|
||||
csharp_style_expression_bodied_properties = true:silent
|
||||
|
||||
# Pattern matching preferences
|
||||
csharp_style_pattern_matching_over_as_with_null_check = true:silent
|
||||
csharp_style_pattern_matching_over_is_with_cast_check = true:silent
|
||||
|
||||
# Null-checking preferences
|
||||
csharp_style_conditional_delegate_call = true:suggestion
|
||||
|
||||
# Modifier preferences
|
||||
csharp_preferred_modifier_order = public,private,protected,internal,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,volatile,async
|
||||
|
||||
# Code-block preferences
|
||||
csharp_prefer_braces = false:suggestion
|
||||
|
||||
# Expression-level preferences
|
||||
csharp_prefer_simple_default_expression = true:suggestion
|
||||
csharp_style_pattern_local_over_anonymous_function = true:suggestion
|
||||
|
||||
#### C# Formatting Rules ####
|
||||
|
||||
# New line preferences
|
||||
csharp_new_line_before_catch = true
|
||||
csharp_new_line_before_else = true
|
||||
csharp_new_line_before_finally = true
|
||||
csharp_new_line_before_members_in_anonymous_types = true
|
||||
csharp_new_line_before_members_in_object_initializers = true
|
||||
csharp_new_line_before_open_brace = all
|
||||
csharp_new_line_between_query_expression_clauses = true
|
||||
|
||||
# Indentation preferences
|
||||
csharp_indent_block_contents = true
|
||||
csharp_indent_braces = false
|
||||
csharp_indent_case_contents = true
|
||||
csharp_indent_case_contents_when_block = true
|
||||
csharp_indent_labels = one_less_than_current
|
||||
csharp_indent_switch_labels = true
|
||||
|
||||
# Space preferences
|
||||
csharp_space_after_cast = false
|
||||
csharp_space_after_colon_in_inheritance_clause = true
|
||||
csharp_space_after_comma = true
|
||||
csharp_space_after_dot = false
|
||||
csharp_space_after_keywords_in_control_flow_statements = true
|
||||
csharp_space_after_semicolon_in_for_statement = true
|
||||
csharp_space_around_binary_operators = before_and_after
|
||||
csharp_space_around_declaration_statements = false
|
||||
csharp_space_before_colon_in_inheritance_clause = true
|
||||
csharp_space_before_comma = false
|
||||
csharp_space_before_dot = false
|
||||
csharp_space_before_open_square_brackets = false
|
||||
csharp_space_before_semicolon_in_for_statement = false
|
||||
csharp_space_between_empty_square_brackets = false
|
||||
csharp_space_between_method_call_empty_parameter_list_parentheses = false
|
||||
csharp_space_between_method_call_name_and_opening_parenthesis = false
|
||||
csharp_space_between_method_call_parameter_list_parentheses = false
|
||||
csharp_space_between_method_declaration_empty_parameter_list_parentheses = false
|
||||
csharp_space_between_method_declaration_name_and_open_parenthesis = false
|
||||
csharp_space_between_method_declaration_parameter_list_parentheses = false
|
||||
csharp_space_between_parentheses = false
|
||||
csharp_space_between_square_brackets = false
|
||||
|
||||
# Wrapping preferences
|
||||
csharp_preserve_single_line_blocks = true
|
||||
csharp_preserve_single_line_statements = true
|
||||
|
||||
|
||||
# Naming Symbols
|
||||
# constant_fields - Define constant fields
|
||||
dotnet_naming_symbols.constant_fields.applicable_kinds = field
|
||||
dotnet_naming_symbols.constant_fields.required_modifiers = const
|
||||
# non_private_readonly_fields - Define public, internal and protected readonly fields
|
||||
dotnet_naming_symbols.non_private_readonly_fields.applicable_accessibilities = public, internal, protected
|
||||
dotnet_naming_symbols.non_private_readonly_fields.applicable_kinds = field
|
||||
dotnet_naming_symbols.non_private_readonly_fields.required_modifiers = readonly
|
||||
# static_readonly_fields - Define static and readonly fields
|
||||
dotnet_naming_symbols.static_readonly_fields.applicable_kinds = field
|
||||
dotnet_naming_symbols.static_readonly_fields.required_modifiers = static, readonly
|
||||
# private_readonly_fields - Define private readonly fields
|
||||
dotnet_naming_symbols.private_readonly_fields.applicable_accessibilities = private
|
||||
dotnet_naming_symbols.private_readonly_fields.applicable_kinds = field
|
||||
dotnet_naming_symbols.private_readonly_fields.required_modifiers = readonly
|
||||
# public_internal_fields - Define public and internal fields
|
||||
dotnet_naming_symbols.public_internal_protected_fields.applicable_accessibilities = public, internal, protected
|
||||
dotnet_naming_symbols.public_internal_protected_fields.applicable_kinds = field
|
||||
# private_protected_fields - Define private and protected fields
|
||||
dotnet_naming_symbols.private_protected_fields.applicable_accessibilities = private, protected
|
||||
dotnet_naming_symbols.private_protected_fields.applicable_kinds = field
|
||||
# public_symbols - Define any public symbol
|
||||
dotnet_naming_symbols.public_symbols.applicable_accessibilities = public, internal, protected, protected_internal
|
||||
dotnet_naming_symbols.public_symbols.applicable_kinds = method, property, event, delegate
|
||||
# parameters - Defines any parameter
|
||||
dotnet_naming_symbols.parameters.applicable_kinds = parameter
|
||||
# non_interface_types - Defines class, struct, enum and delegate types
|
||||
dotnet_naming_symbols.non_interface_types.applicable_kinds = class, struct, enum, delegate
|
||||
# interface_types - Defines interfaces
|
||||
dotnet_naming_symbols.interface_types.applicable_kinds = interface
|
||||
|
||||
# Naming Styles
|
||||
# camel_case - Define the camelCase style
|
||||
dotnet_naming_style.camel_case.capitalization = camel_case
|
||||
# pascal_case - Define the Pascal_case style
|
||||
dotnet_naming_style.pascal_case.capitalization = pascal_case
|
||||
# first_upper - The first character must start with an upper-case character
|
||||
dotnet_naming_style.first_upper.capitalization = first_word_upper
|
||||
# prefix_interface_interface_with_i - Interfaces must be PascalCase and the first character of an interface must be an 'I'
|
||||
dotnet_naming_style.prefix_interface_interface_with_i.capitalization = pascal_case
|
||||
dotnet_naming_style.prefix_interface_interface_with_i.required_prefix = I
|
||||
|
||||
# Naming Rules
|
||||
|
||||
# Async
|
||||
dotnet_naming_rule.async_methods_end_in_async.severity = silent
|
||||
dotnet_naming_rule.async_methods_end_in_async.symbols = any_async_methods
|
||||
dotnet_naming_rule.async_methods_end_in_async.style = end_in_async
|
||||
|
||||
dotnet_naming_symbols.any_async_methods.applicable_kinds = method
|
||||
dotnet_naming_symbols.any_async_methods.applicable_accessibilities = *
|
||||
dotnet_naming_symbols.any_async_methods.required_modifiers = async
|
||||
|
||||
dotnet_naming_style.end_in_async.required_suffix = Async
|
||||
dotnet_naming_style.end_in_async.capitalization = pascal_case
|
||||
|
||||
# Constant fields must be PascalCase
|
||||
dotnet_naming_rule.constant_fields_must_be_pascal_case.severity = silent
|
||||
dotnet_naming_rule.constant_fields_must_be_pascal_case.symbols = constant_fields
|
||||
dotnet_naming_rule.constant_fields_must_be_pascal_case.style = pascal_case
|
||||
# Public, internal and protected readonly fields must be PascalCase
|
||||
dotnet_naming_rule.non_private_readonly_fields_must_be_pascal_case.severity = silent
|
||||
dotnet_naming_rule.non_private_readonly_fields_must_be_pascal_case.symbols = non_private_readonly_fields
|
||||
dotnet_naming_rule.non_private_readonly_fields_must_be_pascal_case.style = pascal_case
|
||||
# Static readonly fields must be PascalCase
|
||||
dotnet_naming_rule.static_readonly_fields_must_be_pascal_case.severity = silent
|
||||
dotnet_naming_rule.static_readonly_fields_must_be_pascal_case.symbols = static_readonly_fields
|
||||
dotnet_naming_rule.static_readonly_fields_must_be_pascal_case.style = pascal_case
|
||||
# Private readonly fields must be camelCase
|
||||
dotnet_naming_rule.private_readonly_fields_must_be_camel_case.severity = silent
|
||||
dotnet_naming_rule.private_readonly_fields_must_be_camel_case.symbols = private_readonly_fields
|
||||
dotnet_naming_rule.private_readonly_fields_must_be_camel_case.style = camel_case
|
||||
# Public and internal fields must be PascalCase
|
||||
dotnet_naming_rule.public_internal_protected_fields_must_be_pascal_case.severity = silent
|
||||
dotnet_naming_rule.public_internal_protected_fields_must_be_pascal_case.symbols = public_internal_protected_fields
|
||||
dotnet_naming_rule.public_internal_protected_fields_must_be_pascal_case.style = pascal_case
|
||||
# Private and protected fields must be camelCase
|
||||
dotnet_naming_rule.private_fields_must_be_camel_case.severity = silent
|
||||
dotnet_naming_rule.private_fields_must_be_camel_case.symbols = private_protected_fields
|
||||
dotnet_naming_rule.private_fields_must_be_camel_case.style = prefix_private_field_with_underscore
|
||||
# Public members must be capitalized
|
||||
dotnet_naming_rule.public_members_must_be_capitalized.severity = silent
|
||||
dotnet_naming_rule.public_members_must_be_capitalized.symbols = public_symbols
|
||||
dotnet_naming_rule.public_members_must_be_capitalized.style = first_upper
|
||||
# Parameters must be camelCase
|
||||
dotnet_naming_rule.parameters_must_be_camel_case.severity = silent
|
||||
dotnet_naming_rule.parameters_must_be_camel_case.symbols = parameters
|
||||
dotnet_naming_rule.parameters_must_be_camel_case.style = camel_case
|
||||
# Class, struct, enum and delegates must be PascalCase
|
||||
dotnet_naming_rule.non_interface_types_must_be_pascal_case.severity = silent
|
||||
dotnet_naming_rule.non_interface_types_must_be_pascal_case.symbols = non_interface_types
|
||||
dotnet_naming_rule.non_interface_types_must_be_pascal_case.style = pascal_case
|
||||
# Interfaces must be PascalCase and start with an 'I'
|
||||
dotnet_naming_rule.interface_types_must_be_prefixed_with_i.severity = silent
|
||||
dotnet_naming_rule.interface_types_must_be_prefixed_with_i.symbols = interface_types
|
||||
dotnet_naming_rule.interface_types_must_be_prefixed_with_i.style = prefix_interface_interface_with_i
|
||||
# prefix_private_field_with_underscore - Private fields must be prefixed with _
|
||||
dotnet_naming_style.prefix_private_field_with_underscore.capitalization = camel_case
|
||||
dotnet_naming_style.prefix_private_field_with_underscore.required_prefix = _
|
||||
|
||||
# Code files
|
||||
[*.{cs,vb}]
|
||||
|
||||
# Migrate back from old Toolkit.ruleset
|
||||
dotnet_diagnostic.CA1001.severity = warning
|
||||
dotnet_diagnostic.CA1009.severity = warning
|
||||
dotnet_diagnostic.CA1016.severity = warning
|
||||
dotnet_diagnostic.CA1033.severity = warning
|
||||
dotnet_diagnostic.CA1049.severity = warning
|
||||
dotnet_diagnostic.CA1060.severity = warning
|
||||
dotnet_diagnostic.CA1061.severity = warning
|
||||
dotnet_diagnostic.CA1063.severity = warning
|
||||
dotnet_diagnostic.CA1065.severity = warning
|
||||
dotnet_diagnostic.CA1301.severity = warning
|
||||
dotnet_diagnostic.CA1400.severity = warning
|
||||
dotnet_diagnostic.CA1401.severity = warning
|
||||
dotnet_diagnostic.CA1403.severity = warning
|
||||
dotnet_diagnostic.CA1404.severity = warning
|
||||
dotnet_diagnostic.CA1405.severity = warning
|
||||
dotnet_diagnostic.CA1410.severity = warning
|
||||
dotnet_diagnostic.CA1415.severity = warning
|
||||
dotnet_diagnostic.CA1821.severity = warning
|
||||
dotnet_diagnostic.CA1900.severity = warning
|
||||
dotnet_diagnostic.CA1901.severity = warning
|
||||
dotnet_diagnostic.CA2002.severity = warning
|
||||
dotnet_diagnostic.CA2100.severity = warning
|
||||
dotnet_diagnostic.CA2101.severity = warning
|
||||
dotnet_diagnostic.CA2108.severity = warning
|
||||
dotnet_diagnostic.CA2111.severity = warning
|
||||
dotnet_diagnostic.CA2112.severity = warning
|
||||
dotnet_diagnostic.CA2114.severity = warning
|
||||
dotnet_diagnostic.CA2116.severity = warning
|
||||
dotnet_diagnostic.CA2117.severity = warning
|
||||
dotnet_diagnostic.CA2122.severity = warning
|
||||
dotnet_diagnostic.CA2123.severity = warning
|
||||
dotnet_diagnostic.CA2124.severity = warning
|
||||
dotnet_diagnostic.CA2126.severity = warning
|
||||
dotnet_diagnostic.CA2131.severity = warning
|
||||
dotnet_diagnostic.CA2132.severity = warning
|
||||
dotnet_diagnostic.CA2133.severity = warning
|
||||
dotnet_diagnostic.CA2134.severity = warning
|
||||
dotnet_diagnostic.CA2137.severity = warning
|
||||
dotnet_diagnostic.CA2138.severity = warning
|
||||
dotnet_diagnostic.CA2140.severity = warning
|
||||
dotnet_diagnostic.CA2141.severity = warning
|
||||
dotnet_diagnostic.CA2146.severity = warning
|
||||
dotnet_diagnostic.CA2147.severity = warning
|
||||
dotnet_diagnostic.CA2149.severity = warning
|
||||
dotnet_diagnostic.CA2200.severity = warning
|
||||
dotnet_diagnostic.CA2202.severity = warning
|
||||
dotnet_diagnostic.CA2207.severity = warning
|
||||
dotnet_diagnostic.CA2212.severity = warning
|
||||
dotnet_diagnostic.CA2213.severity = warning
|
||||
dotnet_diagnostic.CA2214.severity = warning
|
||||
dotnet_diagnostic.CA2216.severity = warning
|
||||
dotnet_diagnostic.CA2220.severity = warning
|
||||
dotnet_diagnostic.CA2229.severity = warning
|
||||
dotnet_diagnostic.CA2231.severity = warning
|
||||
dotnet_diagnostic.CA2232.severity = warning
|
||||
dotnet_diagnostic.CA2235.severity = warning
|
||||
dotnet_diagnostic.CA2236.severity = warning
|
||||
dotnet_diagnostic.CA2237.severity = warning
|
||||
dotnet_diagnostic.CA2238.severity = warning
|
||||
dotnet_diagnostic.CA2240.severity = warning
|
||||
dotnet_diagnostic.CA2241.severity = warning
|
||||
dotnet_diagnostic.CA2242.severity = warning
|
||||
dotnet_diagnostic.CA1031.severity = none # Disable https://docs.microsoft.com/en-us/visualstudio/code-quality/ca1031?view=vs-2019
|
||||
dotnet_diagnostic.SA1011.severity = none
|
||||
dotnet_diagnostic.SA1101.severity = none
|
||||
dotnet_diagnostic.SA1118.severity = none
|
||||
dotnet_diagnostic.SA1200.severity = none
|
||||
dotnet_diagnostic.SA1201.severity = none
|
||||
dotnet_diagnostic.SA1202.severity = none
|
||||
dotnet_diagnostic.SA1309.severity = none
|
||||
dotnet_diagnostic.SA1310.severity = none
|
||||
dotnet_diagnostic.SA1600.severity = none
|
||||
dotnet_diagnostic.SA1602.severity = none
|
||||
dotnet_diagnostic.SA1611.severity = none
|
||||
dotnet_diagnostic.SA1633.severity = none
|
||||
dotnet_diagnostic.SA1634.severity = none
|
||||
dotnet_diagnostic.SA1652.severity = none
|
||||
|
||||
dotnet_diagnostic.SA1629.severity = none # DocumentationTextMustEndWithAPeriod: Let's enable this rule back when we shift to WinUI3 (v8.x). If we do it now, it would mean more than 400 file changes.
|
||||
dotnet_diagnostic.SA1413.severity = none # UseTrailingCommasInMultiLineInitializers: This would also mean a lot of changes at the end of all multiline intializers. It's also debatable if we want this or not.
|
||||
dotnet_diagnostic.SA1314.severity = none # TypeParameterNamesMustBeginWithT: We do have a few templates that don't start with T. We need to double check that changing this is not a breaking change. If not, we can re-enable this.
|
||||
@ -0,0 +1,204 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
// Source: https://github.com/windows-toolkit/WindowsCommunityToolkit/tree/master/Microsoft.Toolkit.Uwp.UI.Controls/DropShadowPanel
|
||||
|
||||
namespace Notepads.Controls
|
||||
{
|
||||
using Windows.UI;
|
||||
using Windows.UI.Composition;
|
||||
using Windows.UI.Xaml;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="DropShadowPanel"/> control allows the creation of a DropShadow for any Xaml FrameworkElement in markup
|
||||
/// making it easier to add shadows to Xaml without having to directly drop down to Windows.UI.Composition APIs.
|
||||
/// </summary>
|
||||
public partial class DropShadowPanel
|
||||
{
|
||||
/// <summary>
|
||||
/// Identifies the <see cref="BlurRadius"/> dependency property.
|
||||
/// </summary>
|
||||
public static readonly DependencyProperty BlurRadiusProperty =
|
||||
DependencyProperty.Register(nameof(BlurRadius), typeof(double), typeof(DropShadowPanel), new PropertyMetadata(9.0, OnBlurRadiusChanged));
|
||||
|
||||
/// <summary>
|
||||
/// Identifies the <see cref="Color"/> dependency property.
|
||||
/// </summary>
|
||||
public static readonly DependencyProperty ColorProperty =
|
||||
DependencyProperty.Register(nameof(Color), typeof(Color), typeof(DropShadowPanel), new PropertyMetadata(Colors.Black, OnColorChanged));
|
||||
|
||||
/// <summary>
|
||||
/// Identifies the <see cref="OffsetX"/> dependency property.
|
||||
/// </summary>
|
||||
public static readonly DependencyProperty OffsetXProperty =
|
||||
DependencyProperty.Register(nameof(OffsetX), typeof(double), typeof(DropShadowPanel), new PropertyMetadata(0.0, OnOffsetXChanged));
|
||||
|
||||
/// <summary>
|
||||
/// Identifies the <see cref="OffsetY"/> dependency property.
|
||||
/// </summary>
|
||||
public static readonly DependencyProperty OffsetYProperty =
|
||||
DependencyProperty.Register(nameof(OffsetY), typeof(double), typeof(DropShadowPanel), new PropertyMetadata(0.0, OnOffsetYChanged));
|
||||
|
||||
/// <summary>
|
||||
/// Identifies the <see cref="OffsetZ"/> dependency property.
|
||||
/// </summary>
|
||||
public static readonly DependencyProperty OffsetZProperty =
|
||||
DependencyProperty.Register(nameof(OffsetZ), typeof(double), typeof(DropShadowPanel), new PropertyMetadata(0.0, OnOffsetZChanged));
|
||||
|
||||
/// <summary>
|
||||
/// Identifies the <see cref="ShadowOpacity"/> dependency property.
|
||||
/// </summary>
|
||||
public static readonly DependencyProperty ShadowOpacityProperty =
|
||||
DependencyProperty.Register(nameof(ShadowOpacity), typeof(double), typeof(DropShadowPanel), new PropertyMetadata(1.0, OnShadowOpacityChanged));
|
||||
|
||||
/// <summary>
|
||||
/// Identifies the <see cref="IsMasked"/> dependency property.
|
||||
/// </summary>
|
||||
public static readonly DependencyProperty IsMaskedProperty =
|
||||
DependencyProperty.Register(nameof(IsMasked), typeof(bool), typeof(DropShadowPanel), new PropertyMetadata(true, OnIsMaskedChanged));
|
||||
|
||||
/// <summary>
|
||||
/// Gets DropShadow. Exposes the underlying composition object to allow custom Windows.UI.Composition animations.
|
||||
/// </summary>
|
||||
public DropShadow DropShadow => _dropShadow;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the mask of the underlying <see cref="Windows.UI.Composition.DropShadow"/>.
|
||||
/// Allows for a custom <see cref="Windows.UI.Composition.CompositionBrush"/> to be set.
|
||||
/// </summary>
|
||||
public CompositionBrush Mask
|
||||
{
|
||||
get => _dropShadow?.Mask;
|
||||
|
||||
set
|
||||
{
|
||||
if (_dropShadow != null)
|
||||
{
|
||||
_dropShadow.Mask = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the blur radius of the drop shadow.
|
||||
/// </summary>
|
||||
public double BlurRadius
|
||||
{
|
||||
get => (double)GetValue(BlurRadiusProperty);
|
||||
set => SetValue(BlurRadiusProperty, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the color of the drop shadow.
|
||||
/// </summary>
|
||||
public Color Color
|
||||
{
|
||||
get => (Color)GetValue(ColorProperty);
|
||||
set => SetValue(ColorProperty, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the x offset of the drop shadow.
|
||||
/// </summary>
|
||||
public double OffsetX
|
||||
{
|
||||
get => (double)GetValue(OffsetXProperty);
|
||||
set => SetValue(OffsetXProperty, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the y offset of the drop shadow.
|
||||
/// </summary>
|
||||
public double OffsetY
|
||||
{
|
||||
get => (double)GetValue(OffsetYProperty);
|
||||
set => SetValue(OffsetYProperty, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the z offset of the drop shadow.
|
||||
/// </summary>
|
||||
public double OffsetZ
|
||||
{
|
||||
get => (double)GetValue(OffsetZProperty);
|
||||
set => SetValue(OffsetZProperty, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the opacity of the drop shadow.
|
||||
/// </summary>
|
||||
public double ShadowOpacity
|
||||
{
|
||||
get => (double)GetValue(ShadowOpacityProperty);
|
||||
set => SetValue(ShadowOpacityProperty, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether the panel uses an alpha mask to create a more precise shadow vs. a quicker rectangle shape.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Turn this off to lose fidelity and gain performance of the panel.
|
||||
/// </remarks>
|
||||
public bool IsMasked
|
||||
{
|
||||
get => (bool)GetValue(IsMaskedProperty);
|
||||
set => SetValue(IsMaskedProperty, value);
|
||||
}
|
||||
|
||||
private static void OnBlurRadiusChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
if (d is DropShadowPanel panel)
|
||||
{
|
||||
panel.OnBlurRadiusChanged((double)e.NewValue);
|
||||
}
|
||||
}
|
||||
|
||||
private static void OnColorChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
if (d is DropShadowPanel panel)
|
||||
{
|
||||
panel.OnColorChanged((Color)e.NewValue);
|
||||
}
|
||||
}
|
||||
|
||||
private static void OnOffsetXChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
if (d is DropShadowPanel panel)
|
||||
{
|
||||
panel.OnOffsetXChanged((double)e.NewValue);
|
||||
}
|
||||
}
|
||||
|
||||
private static void OnOffsetYChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
if (d is DropShadowPanel panel)
|
||||
{
|
||||
panel.OnOffsetYChanged((double)e.NewValue);
|
||||
}
|
||||
}
|
||||
|
||||
private static void OnOffsetZChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
if (d is DropShadowPanel panel)
|
||||
{
|
||||
panel.OnOffsetZChanged((double)e.NewValue);
|
||||
}
|
||||
}
|
||||
|
||||
private static void OnShadowOpacityChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
if (d is DropShadowPanel panel)
|
||||
{
|
||||
panel.OnShadowOpacityChanged((double)e.NewValue);
|
||||
}
|
||||
}
|
||||
|
||||
private static void OnIsMaskedChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
if (d is DropShadowPanel panel)
|
||||
{
|
||||
panel.UpdateShadowMask();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,191 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
// Source: https://github.com/windows-toolkit/WindowsCommunityToolkit/tree/master/Microsoft.Toolkit.Uwp.UI.Controls/DropShadowPanel
|
||||
|
||||
namespace Notepads.Controls
|
||||
{
|
||||
using System.Numerics;
|
||||
using Windows.UI;
|
||||
using Windows.UI.Composition;
|
||||
using Windows.UI.Xaml;
|
||||
using Windows.UI.Xaml.Controls;
|
||||
using Windows.UI.Xaml.Hosting;
|
||||
using Windows.UI.Xaml.Shapes;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="DropShadowPanel"/> control allows the creation of a DropShadow for any Xaml FrameworkElement in markup
|
||||
/// making it easier to add shadows to Xaml without having to directly drop down to Windows.UI.Composition APIs.
|
||||
/// </summary>
|
||||
[TemplatePart(Name = PartShadow, Type = typeof(Border))]
|
||||
public partial class DropShadowPanel : ContentControl
|
||||
{
|
||||
private const string PartShadow = "ShadowElement";
|
||||
|
||||
private readonly DropShadow _dropShadow;
|
||||
private readonly SpriteVisual _shadowVisual;
|
||||
private Border _border;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="DropShadowPanel"/> class.
|
||||
/// </summary>
|
||||
public DropShadowPanel()
|
||||
{
|
||||
DefaultStyleKey = typeof(DropShadowPanel);
|
||||
|
||||
Compositor compositor = ElementCompositionPreview.GetElementVisual(this).Compositor;
|
||||
|
||||
_shadowVisual = compositor.CreateSpriteVisual();
|
||||
|
||||
_dropShadow = compositor.CreateDropShadow();
|
||||
_shadowVisual.Shadow = _dropShadow;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Update the visual state of the control when its template is changed.
|
||||
/// </summary>
|
||||
protected override void OnApplyTemplate()
|
||||
{
|
||||
_border = GetTemplateChild(PartShadow) as Border;
|
||||
|
||||
if (_border != null)
|
||||
{
|
||||
ElementCompositionPreview.SetElementChildVisual(_border, _shadowVisual);
|
||||
}
|
||||
|
||||
ConfigureShadowVisualForCastingElement();
|
||||
|
||||
base.OnApplyTemplate();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override void OnContentChanged(object oldContent, object newContent)
|
||||
{
|
||||
if (oldContent != null)
|
||||
{
|
||||
if (oldContent is FrameworkElement oldElement)
|
||||
{
|
||||
oldElement.SizeChanged -= OnSizeChanged;
|
||||
}
|
||||
}
|
||||
|
||||
if (newContent != null)
|
||||
{
|
||||
if (newContent is FrameworkElement newElement)
|
||||
{
|
||||
newElement.SizeChanged += OnSizeChanged;
|
||||
}
|
||||
}
|
||||
|
||||
base.OnContentChanged(oldContent, newContent);
|
||||
}
|
||||
|
||||
private void OnSizeChanged(object sender, SizeChangedEventArgs e)
|
||||
{
|
||||
UpdateShadowSize();
|
||||
}
|
||||
|
||||
private void ConfigureShadowVisualForCastingElement()
|
||||
{
|
||||
UpdateShadowMask();
|
||||
UpdateShadowSize();
|
||||
}
|
||||
|
||||
private void OnBlurRadiusChanged(double newValue)
|
||||
{
|
||||
if (_dropShadow != null)
|
||||
{
|
||||
_dropShadow.BlurRadius = (float)newValue;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnColorChanged(Color newValue)
|
||||
{
|
||||
if (_dropShadow != null)
|
||||
{
|
||||
_dropShadow.Color = newValue;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnOffsetXChanged(double newValue)
|
||||
{
|
||||
if (_dropShadow != null)
|
||||
{
|
||||
UpdateShadowOffset((float)newValue, _dropShadow.Offset.Y, _dropShadow.Offset.Z);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnOffsetYChanged(double newValue)
|
||||
{
|
||||
if (_dropShadow != null)
|
||||
{
|
||||
UpdateShadowOffset(_dropShadow.Offset.X, (float)newValue, _dropShadow.Offset.Z);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnOffsetZChanged(double newValue)
|
||||
{
|
||||
if (_dropShadow != null)
|
||||
{
|
||||
UpdateShadowOffset(_dropShadow.Offset.X, _dropShadow.Offset.Y, (float)newValue);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnShadowOpacityChanged(double newValue)
|
||||
{
|
||||
if (_dropShadow != null)
|
||||
{
|
||||
_dropShadow.Opacity = (float)newValue;
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateShadowMask()
|
||||
{
|
||||
if (Content != null && IsMasked)
|
||||
{
|
||||
CompositionBrush mask = null;
|
||||
|
||||
if (Content is Image image)
|
||||
{
|
||||
mask = image.GetAlphaMask();
|
||||
}
|
||||
else if (Content is Shape shape)
|
||||
{
|
||||
mask = shape.GetAlphaMask();
|
||||
}
|
||||
else if (Content is TextBlock textBlock)
|
||||
{
|
||||
mask = textBlock.GetAlphaMask();
|
||||
}
|
||||
|
||||
_dropShadow.Mask = mask;
|
||||
}
|
||||
else
|
||||
{
|
||||
_dropShadow.Mask = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateShadowOffset(float x, float y, float z)
|
||||
{
|
||||
if (_dropShadow != null)
|
||||
{
|
||||
_dropShadow.Offset = new Vector3(x, y, z);
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateShadowSize()
|
||||
{
|
||||
if (_shadowVisual != null)
|
||||
{
|
||||
Vector2 newSize = new Vector2(0, 0);
|
||||
if (Content is FrameworkElement content)
|
||||
{
|
||||
newSize = new Vector2((float)content.ActualWidth, (float)content.ActualHeight);
|
||||
}
|
||||
|
||||
_shadowVisual.Size = newSize;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,30 @@
|
||||
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:controls="using:Notepads.Controls">
|
||||
|
||||
<Style TargetType="controls:DropShadowPanel">
|
||||
<Setter Property="IsTabStop"
|
||||
Value="False" />
|
||||
<Setter Property="HorizontalAlignment" Value="Stretch"/>
|
||||
<Setter Property="HorizontalContentAlignment" Value="Left"/>
|
||||
<Setter Property="VerticalAlignment" Value="Stretch"/>
|
||||
<Setter Property="VerticalContentAlignment" Value="Stretch"/>
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="controls:DropShadowPanel">
|
||||
<Grid Background="{TemplateBinding Background}"
|
||||
BorderBrush="{TemplateBinding BorderBrush}"
|
||||
BorderThickness="{TemplateBinding BorderThickness}"
|
||||
HorizontalAlignment="{TemplateBinding HorizontalAlignment}"
|
||||
VerticalAlignment="{TemplateBinding VerticalAlignment}">
|
||||
<Border x:Name="ShadowElement"
|
||||
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
|
||||
VerticalAlignment="{TemplateBinding VerticalContentAlignment}" />
|
||||
<ContentPresenter HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
|
||||
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/>
|
||||
</Grid>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
</ResourceDictionary>
|
||||
@ -0,0 +1,158 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
// Source: https://github.com/windows-toolkit/WindowsCommunityToolkit/tree/master/Microsoft.Toolkit.Uwp.UI.Controls/GridSplitter
|
||||
|
||||
namespace Notepads.Controls
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents the control that redistributes space between columns or rows of a Grid control.
|
||||
/// </summary>
|
||||
public partial class GridSplitter
|
||||
{
|
||||
/// <summary>
|
||||
/// Enum to indicate whether GridSplitter resizes Columns or Rows
|
||||
/// </summary>
|
||||
public enum GridResizeDirection
|
||||
{
|
||||
/// <summary>
|
||||
/// Determines whether to resize rows or columns based on its Alignment and
|
||||
/// width compared to height
|
||||
/// </summary>
|
||||
Auto,
|
||||
|
||||
/// <summary>
|
||||
/// Resize columns when dragging Splitter.
|
||||
/// </summary>
|
||||
Columns,
|
||||
|
||||
/// <summary>
|
||||
/// Resize rows when dragging Splitter.
|
||||
/// </summary>
|
||||
Rows
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Enum to indicate what Columns or Rows the GridSplitter resizes
|
||||
/// </summary>
|
||||
public enum GridResizeBehavior
|
||||
{
|
||||
/// <summary>
|
||||
/// Determine which columns or rows to resize based on its Alignment.
|
||||
/// </summary>
|
||||
BasedOnAlignment,
|
||||
|
||||
/// <summary>
|
||||
/// Resize the current and next Columns or Rows.
|
||||
/// </summary>
|
||||
CurrentAndNext,
|
||||
|
||||
/// <summary>
|
||||
/// Resize the previous and current Columns or Rows.
|
||||
/// </summary>
|
||||
PreviousAndCurrent,
|
||||
|
||||
/// <summary>
|
||||
/// Resize the previous and next Columns or Rows.
|
||||
/// </summary>
|
||||
PreviousAndNext
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Enum to indicate the supported gripper cursor types.
|
||||
/// </summary>
|
||||
public enum GripperCursorType
|
||||
{
|
||||
/// <summary>
|
||||
/// Change the cursor based on the splitter direction
|
||||
/// </summary>
|
||||
Default = -1,
|
||||
|
||||
/// <summary>
|
||||
/// Standard Arrow cursor
|
||||
/// </summary>
|
||||
Arrow,
|
||||
|
||||
/// <summary>
|
||||
/// Standard Cross cursor
|
||||
/// </summary>
|
||||
Cross,
|
||||
|
||||
/// <summary>
|
||||
/// Standard Custom cursor
|
||||
/// </summary>
|
||||
Custom,
|
||||
|
||||
/// <summary>
|
||||
/// Standard Hand cursor
|
||||
/// </summary>
|
||||
Hand,
|
||||
|
||||
/// <summary>
|
||||
/// Standard Help cursor
|
||||
/// </summary>
|
||||
Help,
|
||||
|
||||
/// <summary>
|
||||
/// Standard IBeam cursor
|
||||
/// </summary>
|
||||
IBeam,
|
||||
|
||||
/// <summary>
|
||||
/// Standard SizeAll cursor
|
||||
/// </summary>
|
||||
SizeAll,
|
||||
|
||||
/// <summary>
|
||||
/// Standard SizeNortheastSouthwest cursor
|
||||
/// </summary>
|
||||
SizeNortheastSouthwest,
|
||||
|
||||
/// <summary>
|
||||
/// Standard SizeNorthSouth cursor
|
||||
/// </summary>
|
||||
SizeNorthSouth,
|
||||
|
||||
/// <summary>
|
||||
/// Standard SizeNorthwestSoutheast cursor
|
||||
/// </summary>
|
||||
SizeNorthwestSoutheast,
|
||||
|
||||
/// <summary>
|
||||
/// Standard SizeWestEast cursor
|
||||
/// </summary>
|
||||
SizeWestEast,
|
||||
|
||||
/// <summary>
|
||||
/// Standard UniversalNo cursor
|
||||
/// </summary>
|
||||
UniversalNo,
|
||||
|
||||
/// <summary>
|
||||
/// Standard UpArrow cursor
|
||||
/// </summary>
|
||||
UpArrow,
|
||||
|
||||
/// <summary>
|
||||
/// Standard Wait cursor
|
||||
/// </summary>
|
||||
Wait
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Enum to indicate the behavior of window cursor on grid splitter hover
|
||||
/// </summary>
|
||||
public enum SplitterCursorBehavior
|
||||
{
|
||||
/// <summary>
|
||||
/// Update window cursor on Grid Splitter hover
|
||||
/// </summary>
|
||||
ChangeOnSplitterHover,
|
||||
|
||||
/// <summary>
|
||||
/// Update window cursor on Grid Splitter Gripper hover
|
||||
/// </summary>
|
||||
ChangeOnGripperHover
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,302 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
// Source: https://github.com/windows-toolkit/WindowsCommunityToolkit/tree/master/Microsoft.Toolkit.Uwp.UI.Controls/GridSplitter
|
||||
|
||||
namespace Notepads.Controls
|
||||
{
|
||||
using Windows.System;
|
||||
using Windows.UI.Core;
|
||||
using Windows.UI.Xaml;
|
||||
using Windows.UI.Xaml.Controls;
|
||||
using Windows.UI.Xaml.Input;
|
||||
using Windows.UI.Xaml.Media;
|
||||
|
||||
/// <summary>
|
||||
/// Represents the control that redistributes space between columns or rows of a Grid control.
|
||||
/// </summary>
|
||||
public partial class GridSplitter
|
||||
{
|
||||
// Symbols for GripperBar in Segoe MDL2 Assets
|
||||
private const string GripperBarVertical = "\xE784";
|
||||
private const string GripperBarHorizontal = "\xE76F";
|
||||
private const string GripperDisplayFont = "Segoe MDL2 Assets";
|
||||
|
||||
private void GridSplitter_Loaded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
_resizeDirection = GetResizeDirection();
|
||||
_resizeBehavior = GetResizeBehavior();
|
||||
|
||||
// Adding Grip to Grid Splitter
|
||||
if (Element == default(UIElement))
|
||||
{
|
||||
CreateGripperDisplay();
|
||||
Element = _gripperDisplay;
|
||||
}
|
||||
|
||||
if (_hoverWrapper == null)
|
||||
{
|
||||
var hoverWrapper = new GripperHoverWrapper(
|
||||
CursorBehavior == SplitterCursorBehavior.ChangeOnSplitterHover
|
||||
? this
|
||||
: Element,
|
||||
_resizeDirection,
|
||||
GripperCursor,
|
||||
GripperCustomCursorResource);
|
||||
ManipulationStarted += hoverWrapper.SplitterManipulationStarted;
|
||||
ManipulationCompleted += hoverWrapper.SplitterManipulationCompleted;
|
||||
|
||||
_hoverWrapper = hoverWrapper;
|
||||
}
|
||||
}
|
||||
|
||||
private void CreateGripperDisplay()
|
||||
{
|
||||
if (_gripperDisplay == null)
|
||||
{
|
||||
_gripperDisplay = new TextBlock
|
||||
{
|
||||
FontFamily = new FontFamily(GripperDisplayFont),
|
||||
HorizontalAlignment = HorizontalAlignment.Center,
|
||||
VerticalAlignment = VerticalAlignment.Center,
|
||||
Foreground = GripperForeground,
|
||||
Text = _resizeDirection == GridResizeDirection.Columns ? GripperBarVertical : GripperBarHorizontal
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void OnKeyDown(KeyRoutedEventArgs e)
|
||||
{
|
||||
var step = 1;
|
||||
var ctrl = Window.Current.CoreWindow.GetKeyState(VirtualKey.Control);
|
||||
if (ctrl.HasFlag(CoreVirtualKeyStates.Down))
|
||||
{
|
||||
step = 5;
|
||||
}
|
||||
|
||||
if (_resizeDirection == GridResizeDirection.Columns)
|
||||
{
|
||||
if (e.Key == VirtualKey.Left)
|
||||
{
|
||||
HorizontalMove(-step);
|
||||
}
|
||||
else if (e.Key == VirtualKey.Right)
|
||||
{
|
||||
HorizontalMove(step);
|
||||
}
|
||||
else
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
e.Handled = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (_resizeDirection == GridResizeDirection.Rows)
|
||||
{
|
||||
if (e.Key == VirtualKey.Up)
|
||||
{
|
||||
VerticalMove(-step);
|
||||
}
|
||||
else if (e.Key == VirtualKey.Down)
|
||||
{
|
||||
VerticalMove(step);
|
||||
}
|
||||
else
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
base.OnKeyDown(e);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void OnManipulationStarted(ManipulationStartedRoutedEventArgs e)
|
||||
{
|
||||
// saving the previous state
|
||||
PreviousCursor = Window.Current.CoreWindow.PointerCursor;
|
||||
_resizeDirection = GetResizeDirection();
|
||||
_resizeBehavior = GetResizeBehavior();
|
||||
|
||||
if (_resizeDirection == GridResizeDirection.Columns)
|
||||
{
|
||||
Window.Current.CoreWindow.PointerCursor = ColumnsSplitterCursor;
|
||||
}
|
||||
else if (_resizeDirection == GridResizeDirection.Rows)
|
||||
{
|
||||
Window.Current.CoreWindow.PointerCursor = RowSplitterCursor;
|
||||
}
|
||||
|
||||
base.OnManipulationStarted(e);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void OnManipulationCompleted(ManipulationCompletedRoutedEventArgs e)
|
||||
{
|
||||
Window.Current.CoreWindow.PointerCursor = PreviousCursor;
|
||||
|
||||
base.OnManipulationCompleted(e);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void OnManipulationDelta(ManipulationDeltaRoutedEventArgs e)
|
||||
{
|
||||
var horizontalChange = e.Delta.Translation.X;
|
||||
var verticalChange = e.Delta.Translation.Y;
|
||||
|
||||
if (_resizeDirection == GridResizeDirection.Columns)
|
||||
{
|
||||
if (HorizontalMove(horizontalChange))
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
else if (_resizeDirection == GridResizeDirection.Rows)
|
||||
{
|
||||
if (VerticalMove(verticalChange))
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
base.OnManipulationDelta(e);
|
||||
}
|
||||
|
||||
private bool VerticalMove(double verticalChange)
|
||||
{
|
||||
if (CurrentRow == null || SiblingRow == null)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// if current row has fixed height then resize it
|
||||
if (!IsStarRow(CurrentRow))
|
||||
{
|
||||
// No need to check for the row Min height because it is automatically respected
|
||||
if (!SetRowHeight(CurrentRow, verticalChange, GridUnitType.Pixel))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// if sibling row has fixed width then resize it
|
||||
else if (!IsStarRow(SiblingRow))
|
||||
{
|
||||
// Would adding to this column make the current column violate the MinWidth?
|
||||
if (IsValidRowHeight(CurrentRow, verticalChange) == false)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!SetRowHeight(SiblingRow, verticalChange * -1, GridUnitType.Pixel))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// if both row haven't fixed height (auto *)
|
||||
else
|
||||
{
|
||||
// change current row height to the new height with respecting the auto
|
||||
// change sibling row height to the new height relative to current row
|
||||
// respect the other star row height by setting it's height to it's actual height with stars
|
||||
|
||||
// We need to validate current and sibling height to not cause any un expected behavior
|
||||
if (!IsValidRowHeight(CurrentRow, verticalChange) ||
|
||||
!IsValidRowHeight(SiblingRow, verticalChange * -1))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
foreach (var rowDefinition in Resizable.RowDefinitions)
|
||||
{
|
||||
if (rowDefinition == CurrentRow)
|
||||
{
|
||||
SetRowHeight(CurrentRow, verticalChange, GridUnitType.Star);
|
||||
}
|
||||
else if (rowDefinition == SiblingRow)
|
||||
{
|
||||
SetRowHeight(SiblingRow, verticalChange * -1, GridUnitType.Star);
|
||||
}
|
||||
else if (IsStarRow(rowDefinition))
|
||||
{
|
||||
rowDefinition.Height = new GridLength(rowDefinition.ActualHeight, GridUnitType.Star);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private bool HorizontalMove(double horizontalChange)
|
||||
{
|
||||
if (CurrentColumn == null || SiblingColumn == null)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// if current column has fixed width then resize it
|
||||
if (!IsStarColumn(CurrentColumn))
|
||||
{
|
||||
// No need to check for the Column Min width because it is automatically respected
|
||||
if (!SetColumnWidth(CurrentColumn, horizontalChange, GridUnitType.Pixel))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// if sibling column has fixed width then resize it
|
||||
else if (!IsStarColumn(SiblingColumn))
|
||||
{
|
||||
// Would adding to this column make the current column violate the MinWidth?
|
||||
if (IsValidColumnWidth(CurrentColumn, horizontalChange) == false)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!SetColumnWidth(SiblingColumn, horizontalChange * -1, GridUnitType.Pixel))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// if both column haven't fixed width (auto *)
|
||||
else
|
||||
{
|
||||
// change current column width to the new width with respecting the auto
|
||||
// change sibling column width to the new width relative to current column
|
||||
// respect the other star column width by setting it's width to it's actual width with stars
|
||||
|
||||
// We need to validate current and sibling width to not cause any un expected behavior
|
||||
if (!IsValidColumnWidth(CurrentColumn, horizontalChange) ||
|
||||
!IsValidColumnWidth(SiblingColumn, horizontalChange * -1))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
foreach (var columnDefinition in Resizable.ColumnDefinitions)
|
||||
{
|
||||
if (columnDefinition == CurrentColumn)
|
||||
{
|
||||
SetColumnWidth(CurrentColumn, horizontalChange, GridUnitType.Star);
|
||||
}
|
||||
else if (columnDefinition == SiblingColumn)
|
||||
{
|
||||
SetColumnWidth(SiblingColumn, horizontalChange * -1, GridUnitType.Star);
|
||||
}
|
||||
else if (IsStarColumn(columnDefinition))
|
||||
{
|
||||
columnDefinition.Width = new GridLength(columnDefinition.ActualWidth, GridUnitType.Star);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,261 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
// Source: https://github.com/windows-toolkit/WindowsCommunityToolkit/tree/master/Microsoft.Toolkit.Uwp.UI.Controls/GridSplitter
|
||||
|
||||
namespace Notepads.Controls
|
||||
{
|
||||
using Windows.UI.Xaml;
|
||||
using Windows.UI.Xaml.Controls;
|
||||
|
||||
/// <summary>
|
||||
/// Represents the control that redistributes space between columns or rows of a Grid control.
|
||||
/// </summary>
|
||||
public partial class GridSplitter
|
||||
{
|
||||
private static bool IsStarColumn(ColumnDefinition definition)
|
||||
{
|
||||
return ((GridLength)definition.GetValue(ColumnDefinition.WidthProperty)).IsStar;
|
||||
}
|
||||
|
||||
private static bool IsStarRow(RowDefinition definition)
|
||||
{
|
||||
return ((GridLength)definition.GetValue(RowDefinition.HeightProperty)).IsStar;
|
||||
}
|
||||
|
||||
private bool SetColumnWidth(ColumnDefinition columnDefinition, double horizontalChange, GridUnitType unitType)
|
||||
{
|
||||
var newWidth = columnDefinition.ActualWidth + horizontalChange;
|
||||
|
||||
var minWidth = columnDefinition.MinWidth;
|
||||
if (!double.IsNaN(minWidth) && newWidth < minWidth)
|
||||
{
|
||||
newWidth = minWidth;
|
||||
}
|
||||
|
||||
var maxWidth = columnDefinition.MaxWidth;
|
||||
if (!double.IsNaN(maxWidth) && newWidth > maxWidth)
|
||||
{
|
||||
newWidth = maxWidth;
|
||||
}
|
||||
|
||||
if (newWidth > ActualWidth)
|
||||
{
|
||||
columnDefinition.Width = new GridLength(newWidth, unitType);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private bool IsValidColumnWidth(ColumnDefinition columnDefinition, double horizontalChange)
|
||||
{
|
||||
var newWidth = columnDefinition.ActualWidth + horizontalChange;
|
||||
|
||||
var minWidth = columnDefinition.MinWidth;
|
||||
if (!double.IsNaN(minWidth) && newWidth < minWidth)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var maxWidth = columnDefinition.MaxWidth;
|
||||
if (!double.IsNaN(maxWidth) && newWidth > maxWidth)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (newWidth <= ActualWidth)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool SetRowHeight(RowDefinition rowDefinition, double verticalChange, GridUnitType unitType)
|
||||
{
|
||||
var newHeight = rowDefinition.ActualHeight + verticalChange;
|
||||
|
||||
var minHeight = rowDefinition.MinHeight;
|
||||
if (!double.IsNaN(minHeight) && newHeight < minHeight)
|
||||
{
|
||||
newHeight = minHeight;
|
||||
}
|
||||
|
||||
var maxWidth = rowDefinition.MaxHeight;
|
||||
if (!double.IsNaN(maxWidth) && newHeight > maxWidth)
|
||||
{
|
||||
newHeight = maxWidth;
|
||||
}
|
||||
|
||||
if (newHeight > ActualHeight)
|
||||
{
|
||||
rowDefinition.Height = new GridLength(newHeight, unitType);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private bool IsValidRowHeight(RowDefinition rowDefinition, double verticalChange)
|
||||
{
|
||||
var newHeight = rowDefinition.ActualHeight + verticalChange;
|
||||
|
||||
var minHeight = rowDefinition.MinHeight;
|
||||
if (!double.IsNaN(minHeight) && newHeight < minHeight)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var maxHeight = rowDefinition.MaxHeight;
|
||||
if (!double.IsNaN(maxHeight) && newHeight > maxHeight)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (newHeight <= ActualHeight)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Return the targeted Column based on the resize behavior
|
||||
private int GetTargetedColumn()
|
||||
{
|
||||
var currentIndex = Grid.GetColumn(TargetControl);
|
||||
return GetTargetIndex(currentIndex);
|
||||
}
|
||||
|
||||
// Return the sibling Row based on the resize behavior
|
||||
private int GetTargetedRow()
|
||||
{
|
||||
var currentIndex = Grid.GetRow(TargetControl);
|
||||
return GetTargetIndex(currentIndex);
|
||||
}
|
||||
|
||||
// Return the sibling Column based on the resize behavior
|
||||
private int GetSiblingColumn()
|
||||
{
|
||||
var currentIndex = Grid.GetColumn(TargetControl);
|
||||
return GetSiblingIndex(currentIndex);
|
||||
}
|
||||
|
||||
// Return the sibling Row based on the resize behavior
|
||||
private int GetSiblingRow()
|
||||
{
|
||||
var currentIndex = Grid.GetRow(TargetControl);
|
||||
return GetSiblingIndex(currentIndex);
|
||||
}
|
||||
|
||||
// Gets index based on resize behavior for first targeted row/column
|
||||
private int GetTargetIndex(int currentIndex)
|
||||
{
|
||||
switch (_resizeBehavior)
|
||||
{
|
||||
case GridResizeBehavior.CurrentAndNext:
|
||||
return currentIndex;
|
||||
case GridResizeBehavior.PreviousAndNext:
|
||||
return currentIndex - 1;
|
||||
case GridResizeBehavior.PreviousAndCurrent:
|
||||
return currentIndex - 1;
|
||||
default:
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
// Gets index based on resize behavior for second targeted row/column
|
||||
private int GetSiblingIndex(int currentIndex)
|
||||
{
|
||||
switch (_resizeBehavior)
|
||||
{
|
||||
case GridResizeBehavior.CurrentAndNext:
|
||||
return currentIndex + 1;
|
||||
case GridResizeBehavior.PreviousAndNext:
|
||||
return currentIndex + 1;
|
||||
case GridResizeBehavior.PreviousAndCurrent:
|
||||
return currentIndex;
|
||||
default:
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
// Checks the control alignment and Width/Height to detect the control resize direction columns/rows
|
||||
private GridResizeDirection GetResizeDirection()
|
||||
{
|
||||
GridResizeDirection direction = ResizeDirection;
|
||||
|
||||
if (direction == GridResizeDirection.Auto)
|
||||
{
|
||||
// When HorizontalAlignment is Left, Right or Center, resize Columns
|
||||
if (HorizontalAlignment != HorizontalAlignment.Stretch)
|
||||
{
|
||||
direction = GridResizeDirection.Columns;
|
||||
}
|
||||
|
||||
// When VerticalAlignment is Top, Bottom or Center, resize Rows
|
||||
else if (VerticalAlignment != VerticalAlignment.Stretch)
|
||||
{
|
||||
direction = GridResizeDirection.Rows;
|
||||
}
|
||||
|
||||
// Check Width vs Height
|
||||
else if (ActualWidth <= ActualHeight)
|
||||
{
|
||||
direction = GridResizeDirection.Columns;
|
||||
}
|
||||
else
|
||||
{
|
||||
direction = GridResizeDirection.Rows;
|
||||
}
|
||||
}
|
||||
|
||||
return direction;
|
||||
}
|
||||
|
||||
// Get the resize behavior (Which columns/rows should be resized) based on alignment and Direction
|
||||
private GridResizeBehavior GetResizeBehavior()
|
||||
{
|
||||
GridResizeBehavior resizeBehavior = ResizeBehavior;
|
||||
|
||||
if (resizeBehavior == GridResizeBehavior.BasedOnAlignment)
|
||||
{
|
||||
if (_resizeDirection == GridResizeDirection.Columns)
|
||||
{
|
||||
switch (HorizontalAlignment)
|
||||
{
|
||||
case HorizontalAlignment.Left:
|
||||
resizeBehavior = GridResizeBehavior.PreviousAndCurrent;
|
||||
break;
|
||||
case HorizontalAlignment.Right:
|
||||
resizeBehavior = GridResizeBehavior.CurrentAndNext;
|
||||
break;
|
||||
default:
|
||||
resizeBehavior = GridResizeBehavior.PreviousAndNext;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// resize direction is vertical
|
||||
else
|
||||
{
|
||||
switch (VerticalAlignment)
|
||||
{
|
||||
case VerticalAlignment.Top:
|
||||
resizeBehavior = GridResizeBehavior.PreviousAndCurrent;
|
||||
break;
|
||||
case VerticalAlignment.Bottom:
|
||||
resizeBehavior = GridResizeBehavior.CurrentAndNext;
|
||||
break;
|
||||
default:
|
||||
resizeBehavior = GridResizeBehavior.PreviousAndNext;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return resizeBehavior;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,225 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
// Source: https://github.com/windows-toolkit/WindowsCommunityToolkit/tree/master/Microsoft.Toolkit.Uwp.UI.Controls/GridSplitter
|
||||
|
||||
namespace Notepads.Controls
|
||||
{
|
||||
using Windows.UI.Core;
|
||||
using Windows.UI.Xaml;
|
||||
using Windows.UI.Xaml.Media;
|
||||
|
||||
/// <summary>
|
||||
/// Represents the control that redistributes space between columns or rows of a Grid control.
|
||||
/// </summary>
|
||||
public partial class GridSplitter
|
||||
{
|
||||
/// <summary>
|
||||
/// Identifies the <see cref="Element"/> dependency property.
|
||||
/// </summary>
|
||||
public static readonly DependencyProperty ElementProperty
|
||||
= DependencyProperty.Register(
|
||||
nameof(Element),
|
||||
typeof(UIElement),
|
||||
typeof(GridSplitter),
|
||||
new PropertyMetadata(default(UIElement), OnElementPropertyChanged));
|
||||
|
||||
/// <summary>
|
||||
/// Identifies the <see cref="ResizeDirection"/> dependency property.
|
||||
/// </summary>
|
||||
public static readonly DependencyProperty ResizeDirectionProperty
|
||||
= DependencyProperty.Register(
|
||||
nameof(ResizeDirection),
|
||||
typeof(GridResizeDirection),
|
||||
typeof(GridSplitter),
|
||||
new PropertyMetadata(GridResizeDirection.Auto));
|
||||
|
||||
/// <summary>
|
||||
/// Identifies the <see cref="ResizeBehavior"/> dependency property.
|
||||
/// </summary>
|
||||
public static readonly DependencyProperty ResizeBehaviorProperty
|
||||
= DependencyProperty.Register(
|
||||
nameof(ResizeBehavior),
|
||||
typeof(GridResizeBehavior),
|
||||
typeof(GridSplitter),
|
||||
new PropertyMetadata(GridResizeBehavior.BasedOnAlignment));
|
||||
|
||||
/// <summary>
|
||||
/// Identifies the <see cref="GripperForeground"/> dependency property.
|
||||
/// </summary>
|
||||
public static readonly DependencyProperty GripperForegroundProperty
|
||||
= DependencyProperty.Register(
|
||||
nameof(GripperForeground),
|
||||
typeof(Brush),
|
||||
typeof(GridSplitter),
|
||||
new PropertyMetadata(default(Brush), OnGripperForegroundPropertyChanged));
|
||||
|
||||
/// <summary>
|
||||
/// Identifies the <see cref="ParentLevel"/> dependency property.
|
||||
/// </summary>
|
||||
public static readonly DependencyProperty ParentLevelProperty
|
||||
= DependencyProperty.Register(
|
||||
nameof(ParentLevel),
|
||||
typeof(int),
|
||||
typeof(GridSplitter),
|
||||
new PropertyMetadata(default(int)));
|
||||
|
||||
/// <summary>
|
||||
/// Identifies the <see cref="GripperCursor"/> dependency property.
|
||||
/// </summary>
|
||||
public static readonly DependencyProperty GripperCursorProperty =
|
||||
DependencyProperty.RegisterAttached(
|
||||
nameof(GripperCursor),
|
||||
typeof(CoreCursorType?),
|
||||
typeof(GridSplitter),
|
||||
new PropertyMetadata(GripperCursorType.Default, OnGripperCursorPropertyChanged));
|
||||
|
||||
/// <summary>
|
||||
/// Identifies the <see cref="GripperCustomCursorResource"/> dependency property.
|
||||
/// </summary>
|
||||
public static readonly DependencyProperty GripperCustomCursorResourceProperty =
|
||||
DependencyProperty.RegisterAttached(
|
||||
nameof(GripperCustomCursorResource),
|
||||
typeof(uint),
|
||||
typeof(GridSplitter),
|
||||
new PropertyMetadata(GripperCustomCursorDefaultResource, GripperCustomCursorResourcePropertyChanged));
|
||||
|
||||
/// <summary>
|
||||
/// Identifies the <see cref="CursorBehavior"/> dependency property.
|
||||
/// </summary>
|
||||
public static readonly DependencyProperty CursorBehaviorProperty =
|
||||
DependencyProperty.RegisterAttached(
|
||||
nameof(CursorBehavior),
|
||||
typeof(SplitterCursorBehavior),
|
||||
typeof(GridSplitter),
|
||||
new PropertyMetadata(SplitterCursorBehavior.ChangeOnSplitterHover, CursorBehaviorPropertyChanged));
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the visual content of this Grid Splitter
|
||||
/// </summary>
|
||||
public UIElement Element
|
||||
{
|
||||
get => (UIElement)GetValue(ElementProperty);
|
||||
set => SetValue(ElementProperty, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets whether the Splitter resizes the Columns, Rows, or Both.
|
||||
/// </summary>
|
||||
public GridResizeDirection ResizeDirection
|
||||
{
|
||||
get => (GridResizeDirection)GetValue(ResizeDirectionProperty);
|
||||
set => SetValue(ResizeDirectionProperty, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets which Columns or Rows the Splitter resizes.
|
||||
/// </summary>
|
||||
public GridResizeBehavior ResizeBehavior
|
||||
{
|
||||
get => (GridResizeBehavior)GetValue(ResizeBehaviorProperty);
|
||||
set => SetValue(ResizeBehaviorProperty, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the foreground color of grid splitter grip
|
||||
/// </summary>
|
||||
public Brush GripperForeground
|
||||
{
|
||||
get => (Brush)GetValue(GripperForegroundProperty);
|
||||
set => SetValue(GripperForegroundProperty, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the level of the parent grid to resize
|
||||
/// </summary>
|
||||
public int ParentLevel
|
||||
{
|
||||
get => (int)GetValue(ParentLevelProperty);
|
||||
set => SetValue(ParentLevelProperty, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the gripper Cursor type
|
||||
/// </summary>
|
||||
public GripperCursorType GripperCursor
|
||||
{
|
||||
get => (GripperCursorType)GetValue(GripperCursorProperty);
|
||||
set => SetValue(GripperCursorProperty, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the gripper Custom Cursor resource number
|
||||
/// </summary>
|
||||
public int GripperCustomCursorResource
|
||||
{
|
||||
get => (int)GetValue(GripperCustomCursorResourceProperty);
|
||||
set => SetValue(GripperCustomCursorResourceProperty, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets splitter cursor on hover behavior
|
||||
/// </summary>
|
||||
public SplitterCursorBehavior CursorBehavior
|
||||
{
|
||||
get => (SplitterCursorBehavior)GetValue(CursorBehaviorProperty);
|
||||
set => SetValue(CursorBehaviorProperty, value);
|
||||
}
|
||||
|
||||
private static void OnGripperForegroundPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
var gridSplitter = (GridSplitter)d;
|
||||
|
||||
if (gridSplitter._gripperDisplay == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
gridSplitter._gripperDisplay.Foreground = gridSplitter.GripperForeground;
|
||||
}
|
||||
|
||||
private static void OnGripperCursorPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
var gridSplitter = (GridSplitter)d;
|
||||
|
||||
if (gridSplitter._hoverWrapper == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
gridSplitter._hoverWrapper.GripperCursor = gridSplitter.GripperCursor;
|
||||
}
|
||||
|
||||
private static void GripperCustomCursorResourcePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
var gridSplitter = (GridSplitter)d;
|
||||
|
||||
if (gridSplitter._hoverWrapper == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
gridSplitter._hoverWrapper.GripperCustomCursorResource = gridSplitter.GripperCustomCursorResource;
|
||||
}
|
||||
|
||||
private static void CursorBehaviorPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
var gridSplitter = (GridSplitter)d;
|
||||
|
||||
gridSplitter._hoverWrapper?.UpdateHoverElement(gridSplitter.CursorBehavior ==
|
||||
SplitterCursorBehavior.ChangeOnSplitterHover
|
||||
? gridSplitter
|
||||
: gridSplitter.Element);
|
||||
}
|
||||
|
||||
private static void OnElementPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
var gridSplitter = (GridSplitter)d;
|
||||
|
||||
gridSplitter._hoverWrapper?.UpdateHoverElement(gridSplitter.CursorBehavior ==
|
||||
SplitterCursorBehavior.ChangeOnSplitterHover
|
||||
? gridSplitter
|
||||
: gridSplitter.Element);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,246 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
// Source: https://github.com/windows-toolkit/WindowsCommunityToolkit/tree/master/Microsoft.Toolkit.Uwp.UI.Controls/GridSplitter
|
||||
|
||||
namespace Notepads.Controls
|
||||
{
|
||||
using Windows.UI.Core;
|
||||
using Windows.UI.Xaml;
|
||||
using Windows.UI.Xaml.Automation;
|
||||
using Windows.UI.Xaml.Controls;
|
||||
using Windows.UI.Xaml.Input;
|
||||
|
||||
/// <summary>
|
||||
/// Represents the control that redistributes space between columns or rows of a Grid control.
|
||||
/// </summary>
|
||||
public partial class GridSplitter : Control
|
||||
{
|
||||
internal const int GripperCustomCursorDefaultResource = -1;
|
||||
internal static readonly CoreCursor ColumnsSplitterCursor = new CoreCursor(CoreCursorType.SizeWestEast, 1);
|
||||
internal static readonly CoreCursor RowSplitterCursor = new CoreCursor(CoreCursorType.SizeNorthSouth, 1);
|
||||
|
||||
internal CoreCursor PreviousCursor { get; set; }
|
||||
|
||||
private GridResizeDirection _resizeDirection;
|
||||
private GridResizeBehavior _resizeBehavior;
|
||||
private GripperHoverWrapper _hoverWrapper;
|
||||
private TextBlock _gripperDisplay;
|
||||
|
||||
private bool _pressed = false;
|
||||
private bool _dragging = false;
|
||||
private bool _pointerEntered = false;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the target parent grid from level
|
||||
/// </summary>
|
||||
private FrameworkElement TargetControl
|
||||
{
|
||||
get
|
||||
{
|
||||
if (ParentLevel == 0)
|
||||
{
|
||||
return this;
|
||||
}
|
||||
|
||||
var parent = Parent;
|
||||
for (int i = 2; i < ParentLevel; i++)
|
||||
{
|
||||
if (parent is FrameworkElement frameworkElement)
|
||||
{
|
||||
parent = frameworkElement.Parent;
|
||||
}
|
||||
}
|
||||
|
||||
return parent as FrameworkElement;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets GridSplitter Container Grid
|
||||
/// </summary>
|
||||
private Grid Resizable => TargetControl?.Parent as Grid;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current Column definition of the parent Grid
|
||||
/// </summary>
|
||||
private ColumnDefinition CurrentColumn
|
||||
{
|
||||
get
|
||||
{
|
||||
if (Resizable == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var gridSplitterTargetedColumnIndex = GetTargetedColumn();
|
||||
|
||||
if ((gridSplitterTargetedColumnIndex >= 0)
|
||||
&& (gridSplitterTargetedColumnIndex < Resizable.ColumnDefinitions.Count))
|
||||
{
|
||||
return Resizable.ColumnDefinitions[gridSplitterTargetedColumnIndex];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the Sibling Column definition of the parent Grid
|
||||
/// </summary>
|
||||
private ColumnDefinition SiblingColumn
|
||||
{
|
||||
get
|
||||
{
|
||||
if (Resizable == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var gridSplitterSiblingColumnIndex = GetSiblingColumn();
|
||||
|
||||
if ((gridSplitterSiblingColumnIndex >= 0)
|
||||
&& (gridSplitterSiblingColumnIndex < Resizable.ColumnDefinitions.Count))
|
||||
{
|
||||
return Resizable.ColumnDefinitions[gridSplitterSiblingColumnIndex];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current Row definition of the parent Grid
|
||||
/// </summary>
|
||||
private RowDefinition CurrentRow
|
||||
{
|
||||
get
|
||||
{
|
||||
if (Resizable == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var gridSplitterTargetedRowIndex = GetTargetedRow();
|
||||
|
||||
if ((gridSplitterTargetedRowIndex >= 0)
|
||||
&& (gridSplitterTargetedRowIndex < Resizable.RowDefinitions.Count))
|
||||
{
|
||||
return Resizable.RowDefinitions[gridSplitterTargetedRowIndex];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the Sibling Row definition of the parent Grid
|
||||
/// </summary>
|
||||
private RowDefinition SiblingRow
|
||||
{
|
||||
get
|
||||
{
|
||||
if (Resizable == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var gridSplitterSiblingRowIndex = GetSiblingRow();
|
||||
|
||||
if ((gridSplitterSiblingRowIndex >= 0)
|
||||
&& (gridSplitterSiblingRowIndex < Resizable.RowDefinitions.Count))
|
||||
{
|
||||
return Resizable.RowDefinitions[gridSplitterSiblingRowIndex];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="GridSplitter"/> class.
|
||||
/// </summary>
|
||||
public GridSplitter()
|
||||
{
|
||||
DefaultStyleKey = typeof(GridSplitter);
|
||||
Loaded += GridSplitter_Loaded;
|
||||
string automationName = "GridSpliter";
|
||||
AutomationProperties.SetName(this, automationName);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void OnApplyTemplate()
|
||||
{
|
||||
base.OnApplyTemplate();
|
||||
|
||||
// Unhook registered events
|
||||
Loaded -= GridSplitter_Loaded;
|
||||
PointerEntered -= GridSplitter_PointerEntered;
|
||||
PointerExited -= GridSplitter_PointerExited;
|
||||
PointerPressed -= GridSplitter_PointerPressed;
|
||||
PointerReleased -= GridSplitter_PointerReleased;
|
||||
ManipulationStarted -= GridSplitter_ManipulationStarted;
|
||||
ManipulationCompleted -= GridSplitter_ManipulationCompleted;
|
||||
|
||||
_hoverWrapper?.UnhookEvents();
|
||||
|
||||
// Register Events
|
||||
Loaded += GridSplitter_Loaded;
|
||||
PointerEntered += GridSplitter_PointerEntered;
|
||||
PointerExited += GridSplitter_PointerExited;
|
||||
PointerPressed += GridSplitter_PointerPressed;
|
||||
PointerReleased += GridSplitter_PointerReleased;
|
||||
ManipulationStarted += GridSplitter_ManipulationStarted;
|
||||
ManipulationCompleted += GridSplitter_ManipulationCompleted;
|
||||
|
||||
_hoverWrapper?.UpdateHoverElement(Element);
|
||||
|
||||
ManipulationMode = ManipulationModes.TranslateX | ManipulationModes.TranslateY;
|
||||
}
|
||||
|
||||
private void GridSplitter_PointerReleased(object sender, PointerRoutedEventArgs e)
|
||||
{
|
||||
_pressed = false;
|
||||
VisualStateManager.GoToState(this, _pointerEntered ? "PointerOver" : "Normal", true);
|
||||
}
|
||||
|
||||
private void GridSplitter_PointerPressed(object sender, PointerRoutedEventArgs e)
|
||||
{
|
||||
_pressed = true;
|
||||
VisualStateManager.GoToState(this, "Pressed", true);
|
||||
}
|
||||
|
||||
private void GridSplitter_PointerExited(object sender, PointerRoutedEventArgs e)
|
||||
{
|
||||
_pointerEntered = false;
|
||||
|
||||
if (!_pressed && !_dragging)
|
||||
{
|
||||
VisualStateManager.GoToState(this, "Normal", true);
|
||||
}
|
||||
}
|
||||
|
||||
private void GridSplitter_PointerEntered(object sender, PointerRoutedEventArgs e)
|
||||
{
|
||||
_pointerEntered = true;
|
||||
|
||||
if (!_pressed && !_dragging)
|
||||
{
|
||||
VisualStateManager.GoToState(this, "PointerOver", true);
|
||||
}
|
||||
}
|
||||
|
||||
private void GridSplitter_ManipulationCompleted(object sender, ManipulationCompletedRoutedEventArgs e)
|
||||
{
|
||||
_dragging = false;
|
||||
_pressed = false;
|
||||
VisualStateManager.GoToState(this, _pointerEntered ? "PointerOver" : "Normal", true);
|
||||
}
|
||||
|
||||
private void GridSplitter_ManipulationStarted(object sender, ManipulationStartedRoutedEventArgs e)
|
||||
{
|
||||
_dragging = true;
|
||||
VisualStateManager.GoToState(this, "Pressed", true);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,52 @@
|
||||
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:local="using:Notepads.Controls">
|
||||
|
||||
<ResourceDictionary.ThemeDictionaries>
|
||||
<ResourceDictionary x:Key="Default">
|
||||
<SolidColorBrush x:Key="SystemControlSplitterPointerOver" Color="{ThemeResource SystemBaseLowColor}" />
|
||||
<SolidColorBrush x:Key="SystemControlSplitterPressed" Color="{ThemeResource SystemBaseHighColor}" />
|
||||
</ResourceDictionary>
|
||||
<ResourceDictionary x:Key="HighContrast">
|
||||
<SolidColorBrush x:Key="SystemControlSplitterPointerOver" Color="{ThemeResource SystemColorHighlightColor}" />
|
||||
<SolidColorBrush x:Key="SystemControlSplitterPressed" Color="{ThemeResource SystemColorHighlightColor}" />
|
||||
</ResourceDictionary>
|
||||
</ResourceDictionary.ThemeDictionaries>
|
||||
|
||||
<Style TargetType="local:GridSplitter">
|
||||
<Setter Property="IsTabStop" Value="True"></Setter>
|
||||
<Setter Property="UseSystemFocusVisuals" Value="True"></Setter>
|
||||
<Setter Property="HorizontalAlignment" Value="Stretch"></Setter>
|
||||
<Setter Property="VerticalAlignment" Value="Stretch"></Setter>
|
||||
<Setter Property="IsFocusEngagementEnabled" Value="True"></Setter>
|
||||
<Setter Property="MinWidth" Value="16"></Setter>
|
||||
<Setter Property="MinHeight" Value="16"></Setter>
|
||||
<Setter Property="Background" Value="{ThemeResource SystemControlHighlightChromeHighBrush}"></Setter>
|
||||
<Setter Property="GripperForeground" Value="{ThemeResource SystemControlForegroundAltHighBrush}" />
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="local:GridSplitter">
|
||||
<Grid x:Name="RootGrid" Background="{TemplateBinding Background}">
|
||||
<VisualStateManager.VisualStateGroups>
|
||||
<VisualStateGroup x:Name="GridSplitterStates">
|
||||
<VisualState x:Name="Normal" />
|
||||
<VisualState x:Name="PointerOver">
|
||||
<VisualState.Setters>
|
||||
<Setter Target="RootGrid.Background" Value="{ThemeResource SystemControlSplitterPointerOver}" />
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
<VisualState x:Name="Pressed">
|
||||
<VisualState.Setters>
|
||||
<Setter Target="RootGrid.Background" Value="{ThemeResource SystemControlSplitterPressed}" />
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
</VisualStateGroup>
|
||||
</VisualStateManager.VisualStateGroups>
|
||||
<ContentPresenter Content="{TemplateBinding Element}" HorizontalContentAlignment="Stretch" VerticalContentAlignment="Stretch" />
|
||||
</Grid>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
</ResourceDictionary>
|
||||
@ -0,0 +1,154 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
// Source: https://github.com/windows-toolkit/WindowsCommunityToolkit/tree/master/Microsoft.Toolkit.Uwp.UI.Controls/GridSplitter
|
||||
|
||||
namespace Notepads.Controls
|
||||
{
|
||||
using Windows.UI.Core;
|
||||
using Windows.UI.Xaml;
|
||||
using Windows.UI.Xaml.Input;
|
||||
|
||||
internal class GripperHoverWrapper
|
||||
{
|
||||
private readonly GridSplitter.GridResizeDirection _gridSplitterDirection;
|
||||
|
||||
private CoreCursor _splitterPreviousPointer;
|
||||
private CoreCursor _previousCursor;
|
||||
private GridSplitter.GripperCursorType _gripperCursor;
|
||||
private int _gripperCustomCursorResource;
|
||||
private bool _isDragging;
|
||||
private UIElement _element;
|
||||
|
||||
internal GridSplitter.GripperCursorType GripperCursor
|
||||
{
|
||||
get => _gripperCursor;
|
||||
set => _gripperCursor = value;
|
||||
}
|
||||
|
||||
internal int GripperCustomCursorResource
|
||||
{
|
||||
get => _gripperCustomCursorResource;
|
||||
set => _gripperCustomCursorResource = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="GripperHoverWrapper"/> class that add cursor change on hover functionality for GridSplitter.
|
||||
/// </summary>
|
||||
/// <param name="element">UI element to apply cursor change on hover</param>
|
||||
/// <param name="gridSplitterDirection">GridSplitter resize direction</param>
|
||||
/// <param name="gripperCursor">GridSplitter gripper on hover cursor type</param>
|
||||
/// <param name="gripperCustomCursorResource">GridSplitter gripper custom cursor resource number</param>
|
||||
internal GripperHoverWrapper(UIElement element, GridSplitter.GridResizeDirection gridSplitterDirection, GridSplitter.GripperCursorType gripperCursor, int gripperCustomCursorResource)
|
||||
{
|
||||
_gridSplitterDirection = gridSplitterDirection;
|
||||
_gripperCursor = gripperCursor;
|
||||
_gripperCustomCursorResource = gripperCustomCursorResource;
|
||||
_element = element;
|
||||
UnhookEvents();
|
||||
_element.PointerEntered += Element_PointerEntered;
|
||||
_element.PointerExited += Element_PointerExited;
|
||||
}
|
||||
|
||||
internal void UpdateHoverElement(UIElement element)
|
||||
{
|
||||
UnhookEvents();
|
||||
_element = element;
|
||||
_element.PointerEntered += Element_PointerEntered;
|
||||
_element.PointerExited += Element_PointerExited;
|
||||
}
|
||||
|
||||
private void Element_PointerExited(object sender, PointerRoutedEventArgs e)
|
||||
{
|
||||
if (_isDragging)
|
||||
{
|
||||
// if dragging don't update the curser just update the splitter cursor with the last window cursor,
|
||||
// because the splitter is still using the arrow cursor and will revert to original case when drag completes
|
||||
_splitterPreviousPointer = _previousCursor;
|
||||
}
|
||||
else
|
||||
{
|
||||
Window.Current.CoreWindow.PointerCursor = _previousCursor;
|
||||
}
|
||||
}
|
||||
|
||||
private void Element_PointerEntered(object sender, PointerRoutedEventArgs e)
|
||||
{
|
||||
// if not dragging
|
||||
if (!_isDragging)
|
||||
{
|
||||
_previousCursor = _splitterPreviousPointer = Window.Current.CoreWindow.PointerCursor;
|
||||
UpdateDisplayCursor();
|
||||
}
|
||||
|
||||
// if dragging
|
||||
else
|
||||
{
|
||||
_previousCursor = _splitterPreviousPointer;
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateDisplayCursor()
|
||||
{
|
||||
if (_gripperCursor == GridSplitter.GripperCursorType.Default)
|
||||
{
|
||||
if (_gridSplitterDirection == GridSplitter.GridResizeDirection.Columns)
|
||||
{
|
||||
Window.Current.CoreWindow.PointerCursor = GridSplitter.ColumnsSplitterCursor;
|
||||
}
|
||||
else if (_gridSplitterDirection == GridSplitter.GridResizeDirection.Rows)
|
||||
{
|
||||
Window.Current.CoreWindow.PointerCursor = GridSplitter.RowSplitterCursor;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var coreCursor = (CoreCursorType)((int)_gripperCursor);
|
||||
if (_gripperCursor == GridSplitter.GripperCursorType.Custom)
|
||||
{
|
||||
if (_gripperCustomCursorResource > GridSplitter.GripperCustomCursorDefaultResource)
|
||||
{
|
||||
Window.Current.CoreWindow.PointerCursor = new CoreCursor(coreCursor, (uint)_gripperCustomCursorResource);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Window.Current.CoreWindow.PointerCursor = new CoreCursor(coreCursor, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal void SplitterManipulationStarted(object sender, ManipulationStartedRoutedEventArgs e)
|
||||
{
|
||||
if (!(sender is GridSplitter splitter))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_splitterPreviousPointer = splitter.PreviousCursor;
|
||||
_isDragging = true;
|
||||
}
|
||||
|
||||
internal void SplitterManipulationCompleted(object sender, ManipulationCompletedRoutedEventArgs e)
|
||||
{
|
||||
if (!(sender is GridSplitter splitter))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Window.Current.CoreWindow.PointerCursor = splitter.PreviousCursor = _splitterPreviousPointer;
|
||||
_isDragging = false;
|
||||
}
|
||||
|
||||
internal void UnhookEvents()
|
||||
{
|
||||
if (_element == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_element.PointerEntered -= Element_PointerEntered;
|
||||
_element.PointerExited -= Element_PointerExited;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,58 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
// Source: https://github.com/windows-toolkit/WindowsCommunityToolkit/tree/master/Microsoft.Toolkit.Uwp.UI.Controls/InAppNotification
|
||||
|
||||
namespace Notepads.Controls
|
||||
{
|
||||
using System;
|
||||
using Windows.UI.Xaml;
|
||||
using Windows.UI.Xaml.Media.Animation;
|
||||
|
||||
/// <summary>
|
||||
/// In App Notification defines a control to show local notification in the app.
|
||||
/// </summary>
|
||||
public partial class InAppNotification
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the value of the KeyFrameDuration attached Property
|
||||
/// </summary>
|
||||
/// <param name="obj">the KeyFrame where the duration is set</param>
|
||||
/// <returns>Value of KeyFrameDuration</returns>
|
||||
public static TimeSpan GetKeyFrameDuration(DependencyObject obj)
|
||||
{
|
||||
return (TimeSpan)obj.GetValue(KeyFrameDurationProperty);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the value of the KeyFrameDuration attached property
|
||||
/// </summary>
|
||||
/// <param name="obj">The KeyFrame object where the property is attached</param>
|
||||
/// <param name="value">The TimeSpan value to be set as duration</param>
|
||||
public static void SetKeyFrameDuration(DependencyObject obj, TimeSpan value)
|
||||
{
|
||||
obj.SetValue(KeyFrameDurationProperty, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Using a DependencyProperty as the backing store for KeyFrameDuration. This enables animation, styling, binding, etc
|
||||
/// </summary>
|
||||
public static readonly DependencyProperty KeyFrameDurationProperty =
|
||||
DependencyProperty.RegisterAttached("KeyFrameDuration", typeof(TimeSpan), typeof(InAppNotification), new PropertyMetadata(0, OnKeyFrameAnimationChanged));
|
||||
|
||||
private static void OnKeyFrameAnimationChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
if (e.NewValue is TimeSpan ts)
|
||||
{
|
||||
if (d is DoubleKeyFrame dkf)
|
||||
{
|
||||
dkf.KeyTime = KeyTime.FromTimeSpan(ts);
|
||||
}
|
||||
else if (d is ObjectKeyFrame okf)
|
||||
{
|
||||
okf.KeyTime = KeyTime.FromTimeSpan(ts);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,33 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
// Source: https://github.com/windows-toolkit/WindowsCommunityToolkit/tree/master/Microsoft.Toolkit.Uwp.UI.Controls/InAppNotification
|
||||
|
||||
namespace Notepads.Controls
|
||||
{
|
||||
/// <summary>
|
||||
/// In App Notification defines a control to show local notification in the app.
|
||||
/// </summary>
|
||||
public partial class InAppNotification
|
||||
{
|
||||
/// <summary>
|
||||
/// Key of the VisualStateGroup that show/dismiss content
|
||||
/// </summary>
|
||||
private const string GroupContent = "State";
|
||||
|
||||
/// <summary>
|
||||
/// Key of the VisualState when content is showed
|
||||
/// </summary>
|
||||
private const string StateContentVisible = "Visible";
|
||||
|
||||
/// <summary>
|
||||
/// Key of the VisualState when content is dismissed
|
||||
/// </summary>
|
||||
private const string StateContentCollapsed = "Collapsed";
|
||||
|
||||
/// <summary>
|
||||
/// Key of the UI Element that dismiss the control
|
||||
/// </summary>
|
||||
private const string DismissButtonPart = "PART_DismissButton";
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,87 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
// Source: https://github.com/windows-toolkit/WindowsCommunityToolkit/tree/master/Microsoft.Toolkit.Uwp.UI.Controls/InAppNotification
|
||||
|
||||
namespace Notepads.Controls
|
||||
{
|
||||
using System;
|
||||
using Windows.UI.Xaml;
|
||||
using Windows.UI.Xaml.Automation;
|
||||
using Windows.UI.Xaml.Automation.Peers;
|
||||
|
||||
/// <summary>
|
||||
/// In App Notification defines a control to show local notification in the app.
|
||||
/// </summary>
|
||||
public partial class InAppNotification
|
||||
{
|
||||
/// <summary>
|
||||
/// Event raised when the notification is opening
|
||||
/// </summary>
|
||||
public event InAppNotificationOpeningEventHandler Opening;
|
||||
|
||||
/// <summary>
|
||||
/// Event raised when the notification is opened
|
||||
/// </summary>
|
||||
public event EventHandler Opened;
|
||||
|
||||
/// <summary>
|
||||
/// Event raised when the notification is closing
|
||||
/// </summary>
|
||||
public event InAppNotificationClosingEventHandler Closing;
|
||||
|
||||
/// <summary>
|
||||
/// Event raised when the notification is closed
|
||||
/// </summary>
|
||||
public event InAppNotificationClosedEventHandler Closed;
|
||||
|
||||
private AutomationPeer peer;
|
||||
|
||||
private void DismissButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
Dismiss(InAppNotificationDismissKind.User);
|
||||
}
|
||||
|
||||
private void DismissTimer_Tick(object sender, object e)
|
||||
{
|
||||
Dismiss(InAppNotificationDismissKind.Timeout);
|
||||
}
|
||||
|
||||
private void OpenAnimationTimer_Tick(object sender, object e)
|
||||
{
|
||||
lock (_openAnimationTimer)
|
||||
{
|
||||
_openAnimationTimer.Stop();
|
||||
Opened?.Invoke(this, EventArgs.Empty);
|
||||
SetValue(AutomationProperties.NameProperty, "Notification");
|
||||
peer = FrameworkElementAutomationPeer.CreatePeerForElement(ContentTemplateRoot);
|
||||
if (Content?.GetType() == typeof(string))
|
||||
{
|
||||
AutomateTextNotification(Content.ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void AutomateTextNotification(string message)
|
||||
{
|
||||
if (peer != null)
|
||||
{
|
||||
peer.SetFocus();
|
||||
peer.RaiseNotificationEvent(
|
||||
AutomationNotificationKind.Other,
|
||||
AutomationNotificationProcessing.ImportantMostRecent,
|
||||
"New notification" + message,
|
||||
Guid.NewGuid().ToString());
|
||||
}
|
||||
}
|
||||
|
||||
private void ClosingAnimationTimer_Tick(object sender, object e)
|
||||
{
|
||||
lock (_closingAnimationTimer)
|
||||
{
|
||||
_closingAnimationTimer.Stop();
|
||||
Closed?.Invoke(this, new InAppNotificationClosedEventArgs(_lastDismissKind));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,102 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
// Source: https://github.com/windows-toolkit/WindowsCommunityToolkit/tree/master/Microsoft.Toolkit.Uwp.UI.Controls/InAppNotification
|
||||
|
||||
namespace Notepads.Controls
|
||||
{
|
||||
using System;
|
||||
using Windows.UI.Xaml;
|
||||
|
||||
/// <summary>
|
||||
/// In App Notification defines a control to show local notification in the app.
|
||||
/// </summary>
|
||||
public partial class InAppNotification
|
||||
{
|
||||
/// <summary>
|
||||
/// Identifies the <see cref="ShowDismissButton"/> dependency property.
|
||||
/// </summary>
|
||||
public static readonly DependencyProperty ShowDismissButtonProperty =
|
||||
DependencyProperty.Register(nameof(ShowDismissButton), typeof(bool), typeof(InAppNotification), new PropertyMetadata(true, OnShowDismissButtonChanged));
|
||||
|
||||
/// <summary>
|
||||
/// Identifies the <see cref="AnimationDuration"/> dependency property.
|
||||
/// </summary>
|
||||
public static readonly DependencyProperty AnimationDurationProperty =
|
||||
DependencyProperty.Register(nameof(AnimationDuration), typeof(TimeSpan), typeof(InAppNotification), new PropertyMetadata(TimeSpan.FromMilliseconds(100)));
|
||||
|
||||
/// <summary>
|
||||
/// Identifies the <see cref="VerticalOffset"/> dependency property.
|
||||
/// </summary>
|
||||
public static readonly DependencyProperty VerticalOffsetProperty =
|
||||
DependencyProperty.Register(nameof(VerticalOffset), typeof(double), typeof(InAppNotification), new PropertyMetadata(100));
|
||||
|
||||
/// <summary>
|
||||
/// Identifies the <see cref="HorizontalOffset"/> dependency property.
|
||||
/// </summary>
|
||||
public static readonly DependencyProperty HorizontalOffsetProperty =
|
||||
DependencyProperty.Register(nameof(HorizontalOffset), typeof(double), typeof(InAppNotification), new PropertyMetadata(0));
|
||||
|
||||
/// <summary>
|
||||
/// Identifies the <see cref="StackMode"/> dependency property.
|
||||
/// </summary>
|
||||
public static readonly DependencyProperty StackModeProperty =
|
||||
DependencyProperty.Register(nameof(StackMode), typeof(StackMode), typeof(InAppNotification), new PropertyMetadata(StackMode.Replace));
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether to show the Dismiss button of the control.
|
||||
/// </summary>
|
||||
public bool ShowDismissButton
|
||||
{
|
||||
get => (bool)GetValue(ShowDismissButtonProperty);
|
||||
set => SetValue(ShowDismissButtonProperty, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating the duration of the popup animation (in milliseconds).
|
||||
/// </summary>
|
||||
public TimeSpan AnimationDuration
|
||||
{
|
||||
get => (TimeSpan)GetValue(AnimationDurationProperty);
|
||||
set => SetValue(AnimationDurationProperty, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating the vertical offset of the popup animation.
|
||||
/// </summary>
|
||||
public double VerticalOffset
|
||||
{
|
||||
get => (double)GetValue(VerticalOffsetProperty);
|
||||
set => SetValue(VerticalOffsetProperty, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating the horizontal offset of the popup animation.
|
||||
/// </summary>
|
||||
public double HorizontalOffset
|
||||
{
|
||||
get => (double)GetValue(HorizontalOffsetProperty);
|
||||
set => SetValue(HorizontalOffsetProperty, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating the stack mode of the notifications.
|
||||
/// </summary>
|
||||
public StackMode StackMode
|
||||
{
|
||||
get => (StackMode)GetValue(StackModeProperty);
|
||||
set => SetValue(StackModeProperty, value);
|
||||
}
|
||||
|
||||
private static void OnShowDismissButtonChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
var inApNotification = d as InAppNotification;
|
||||
|
||||
if (inApNotification._dismissButton != null)
|
||||
{
|
||||
bool showDismissButton = (bool)e.NewValue;
|
||||
inApNotification._dismissButton.Visibility = showDismissButton ? Visibility.Visible : Visibility.Collapsed;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,274 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
// Source: https://github.com/windows-toolkit/WindowsCommunityToolkit/tree/master/Microsoft.Toolkit.Uwp.UI.Controls/InAppNotification
|
||||
|
||||
namespace Notepads.Controls
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Windows.UI.Xaml;
|
||||
using Windows.UI.Xaml.Controls;
|
||||
|
||||
/// <summary>
|
||||
/// In App Notification defines a control to show local notification in the app.
|
||||
/// </summary>
|
||||
[TemplateVisualState(Name = StateContentVisible, GroupName = GroupContent)]
|
||||
[TemplateVisualState(Name = StateContentCollapsed, GroupName = GroupContent)]
|
||||
[TemplatePart(Name = DismissButtonPart, Type = typeof(Button))]
|
||||
public partial class InAppNotification : ContentControl
|
||||
{
|
||||
private InAppNotificationDismissKind _lastDismissKind;
|
||||
private readonly DispatcherTimer _openAnimationTimer = new DispatcherTimer();
|
||||
private readonly DispatcherTimer _closingAnimationTimer = new DispatcherTimer();
|
||||
private readonly DispatcherTimer _dismissTimer = new DispatcherTimer();
|
||||
private Button _dismissButton;
|
||||
//private VisualStateGroup _visualStateGroup;
|
||||
private readonly List<NotificationOptions> _stackedNotificationOptions = new List<NotificationOptions>();
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="InAppNotification"/> class.
|
||||
/// </summary>
|
||||
public InAppNotification()
|
||||
{
|
||||
DefaultStyleKey = typeof(InAppNotification);
|
||||
|
||||
_dismissTimer.Tick += DismissTimer_Tick;
|
||||
_openAnimationTimer.Tick += OpenAnimationTimer_Tick;
|
||||
_closingAnimationTimer.Tick += ClosingAnimationTimer_Tick;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void OnApplyTemplate()
|
||||
{
|
||||
if (_dismissButton != null)
|
||||
{
|
||||
_dismissButton.Click -= DismissButton_Click;
|
||||
}
|
||||
|
||||
_dismissButton = (Button)GetTemplateChild(DismissButtonPart);
|
||||
//_visualStateGroup = (VisualStateGroup)GetTemplateChild(GroupContent);
|
||||
|
||||
if (_dismissButton != null)
|
||||
{
|
||||
_dismissButton.Visibility = ShowDismissButton ? Visibility.Visible : Visibility.Collapsed;
|
||||
_dismissButton.Click += DismissButton_Click;
|
||||
}
|
||||
|
||||
if (Visibility == Visibility.Visible)
|
||||
{
|
||||
VisualStateManager.GoToState(this, StateContentVisible, true);
|
||||
}
|
||||
else
|
||||
{
|
||||
VisualStateManager.GoToState(this, StateContentCollapsed, true);
|
||||
}
|
||||
|
||||
base.OnApplyTemplate();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Show notification using the current template
|
||||
/// </summary>
|
||||
/// <param name="duration">Displayed duration of the notification in ms (less or equal 0 means infinite duration)</param>
|
||||
public void Show(int duration = 0)
|
||||
{
|
||||
lock (_openAnimationTimer)
|
||||
lock (_closingAnimationTimer)
|
||||
lock (_dismissTimer)
|
||||
{
|
||||
_openAnimationTimer.Stop();
|
||||
_closingAnimationTimer.Stop();
|
||||
_dismissTimer.Stop();
|
||||
|
||||
var eventArgs = new InAppNotificationOpeningEventArgs();
|
||||
Opening?.Invoke(this, eventArgs);
|
||||
|
||||
if (eventArgs.Cancel)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Visibility = Visibility.Visible;
|
||||
VisualStateManager.GoToState(this, StateContentVisible, true);
|
||||
|
||||
_openAnimationTimer.Interval = AnimationDuration;
|
||||
_openAnimationTimer.Start();
|
||||
|
||||
if (duration > 0)
|
||||
{
|
||||
_dismissTimer.Interval = TimeSpan.FromMilliseconds(duration);
|
||||
_dismissTimer.Start();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Show notification using text as the content of the notification
|
||||
/// </summary>
|
||||
/// <param name="text">Text used as the content of the notification</param>
|
||||
/// <param name="duration">Displayed duration of the notification in ms (less or equal 0 means infinite duration)</param>
|
||||
public void Show(string text, int duration = 0)
|
||||
{
|
||||
var notificationOptions = new NotificationOptions
|
||||
{
|
||||
Duration = duration,
|
||||
Content = text
|
||||
};
|
||||
Show(notificationOptions);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Show notification using UIElement as the content of the notification
|
||||
/// </summary>
|
||||
/// <param name="element">UIElement used as the content of the notification</param>
|
||||
/// <param name="duration">Displayed duration of the notification in ms (less or equal 0 means infinite duration)</param>
|
||||
public void Show(UIElement element, int duration = 0)
|
||||
{
|
||||
var notificationOptions = new NotificationOptions
|
||||
{
|
||||
Duration = duration,
|
||||
Content = element
|
||||
};
|
||||
Show(notificationOptions);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Show notification using DataTemplate as the content of the notification
|
||||
/// </summary>
|
||||
/// <param name="dataTemplate">DataTemplate used as the content of the notification</param>
|
||||
/// <param name="duration">Displayed duration of the notification in ms (less or equal 0 means infinite duration)</param>
|
||||
public void Show(DataTemplate dataTemplate, int duration = 0)
|
||||
{
|
||||
var notificationOptions = new NotificationOptions
|
||||
{
|
||||
Duration = duration,
|
||||
Content = dataTemplate
|
||||
};
|
||||
Show(notificationOptions);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Dismiss the notification
|
||||
/// </summary>
|
||||
public void Dismiss()
|
||||
{
|
||||
Dismiss(InAppNotificationDismissKind.Programmatic);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Dismiss the notification
|
||||
/// </summary>
|
||||
/// <param name="dismissKind">Kind of action that triggered dismiss event</param>
|
||||
private void Dismiss(InAppNotificationDismissKind dismissKind)
|
||||
{
|
||||
lock (_openAnimationTimer)
|
||||
lock (_closingAnimationTimer)
|
||||
lock (_dismissTimer)
|
||||
{
|
||||
if (Visibility == Visibility.Visible)
|
||||
{
|
||||
_dismissTimer.Stop();
|
||||
|
||||
// Continue to display notification if on remaining stacked notification
|
||||
if (_stackedNotificationOptions.Any())
|
||||
{
|
||||
_stackedNotificationOptions.RemoveAt(0);
|
||||
|
||||
if (_stackedNotificationOptions.Any())
|
||||
{
|
||||
var notificationOptions = _stackedNotificationOptions[0];
|
||||
|
||||
UpdateContent(notificationOptions);
|
||||
|
||||
if (notificationOptions.Duration > 0)
|
||||
{
|
||||
_dismissTimer.Interval = TimeSpan.FromMilliseconds(notificationOptions.Duration);
|
||||
_dismissTimer.Start();
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
_openAnimationTimer.Stop();
|
||||
_closingAnimationTimer.Stop();
|
||||
|
||||
var closingEventArgs = new InAppNotificationClosingEventArgs(dismissKind);
|
||||
Closing?.Invoke(this, closingEventArgs);
|
||||
|
||||
if (closingEventArgs.Cancel)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
VisualStateManager.GoToState(this, StateContentCollapsed, true);
|
||||
|
||||
_lastDismissKind = dismissKind;
|
||||
|
||||
_closingAnimationTimer.Interval = AnimationDuration;
|
||||
_closingAnimationTimer.Start();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Informs if the notification should be displayed immediately (based on the StackMode)
|
||||
/// </summary>
|
||||
/// <returns>True if notification should be displayed immediately</returns>
|
||||
private bool ShouldDisplayImmediately()
|
||||
{
|
||||
return StackMode != StackMode.QueueBehind ||
|
||||
(StackMode == StackMode.QueueBehind && _stackedNotificationOptions.Count == 0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Update the Content of the notification
|
||||
/// </summary>
|
||||
/// <param name="notificationOptions">Information about the notification to display</param>
|
||||
private void UpdateContent(NotificationOptions notificationOptions)
|
||||
{
|
||||
switch (notificationOptions.Content)
|
||||
{
|
||||
case string text:
|
||||
ContentTemplate = null;
|
||||
Content = text;
|
||||
break;
|
||||
case UIElement element:
|
||||
ContentTemplate = null;
|
||||
Content = element;
|
||||
break;
|
||||
case DataTemplate dataTemplate:
|
||||
ContentTemplate = dataTemplate;
|
||||
Content = null;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle the display of the notification based on the current StackMode
|
||||
/// </summary>
|
||||
/// <param name="notificationOptions">Information about the notification to display</param>
|
||||
private void Show(NotificationOptions notificationOptions)
|
||||
{
|
||||
bool shouldDisplayImmediately = ShouldDisplayImmediately();
|
||||
|
||||
if (StackMode == StackMode.QueueBehind)
|
||||
{
|
||||
_stackedNotificationOptions.Add(notificationOptions);
|
||||
}
|
||||
|
||||
if (StackMode == StackMode.StackInFront)
|
||||
{
|
||||
_stackedNotificationOptions.Insert(0, notificationOptions);
|
||||
}
|
||||
|
||||
if (shouldDisplayImmediately)
|
||||
{
|
||||
UpdateContent(notificationOptions);
|
||||
Show(notificationOptions.Duration);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,35 @@
|
||||
<ResourceDictionary
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:local="using:Notepads.Controls">
|
||||
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<ResourceDictionary Source="Styles/MSEdgeNotificationStyle.xaml" />
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
|
||||
<Style TargetType="local:InAppNotification" x:Key="BaseInAppNotificationsStyle">
|
||||
<Setter Property="Background" Value="{ThemeResource SystemControlBackgroundChromeMediumBrush}" />
|
||||
<Setter Property="Foreground" Value="{ThemeResource SystemControlForegroundBaseHighBrush}" />
|
||||
<Setter Property="BorderBrush" Value="{ThemeResource SystemControlForegroundBaseLowBrush}" />
|
||||
<Setter Property="Visibility" Value="Collapsed" />
|
||||
<Setter Property="BorderThickness" Value="2" />
|
||||
<Setter Property="HorizontalAlignment" Value="Stretch" />
|
||||
<Setter Property="VerticalAlignment" Value="Bottom" />
|
||||
<Setter Property="MinHeight" Value="55" />
|
||||
<Setter Property="FontSize" Value="16" />
|
||||
<Setter Property="RenderTransformOrigin" Value="0.5,1" />
|
||||
<Setter Property="Margin" Value="24,12" />
|
||||
<Setter Property="Padding" Value="24,12" />
|
||||
<Setter Property="MaxWidth" Value="960" />
|
||||
<Setter Property="MinWidth" Value="132" />
|
||||
<Setter Property="AnimationDuration" Value="0:0:0.100" />
|
||||
<Setter Property="VerticalOffset" Value="100" />
|
||||
<Setter Property="HorizontalOffset" Value="0" />
|
||||
<Setter Property="Template" Value="{StaticResource MSEdgeNotificationTemplate}" />
|
||||
</Style>
|
||||
|
||||
<Style TargetType="local:InAppNotification" BasedOn="{StaticResource BaseInAppNotificationsStyle}">
|
||||
<Setter Property="Background" Value="{ThemeResource SystemControlAcrylicElementBrush}" />
|
||||
</Style>
|
||||
|
||||
</ResourceDictionary>
|
||||
@ -0,0 +1,36 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
// Source: https://github.com/windows-toolkit/WindowsCommunityToolkit/tree/master/Microsoft.Toolkit.Uwp.UI.Controls/InAppNotification
|
||||
|
||||
namespace Notepads.Controls
|
||||
{
|
||||
using System;
|
||||
|
||||
/// <summary>
|
||||
/// A delegate for <see cref="InAppNotification"/> dismissing.
|
||||
/// </summary>
|
||||
/// <param name="sender">The sender.</param>
|
||||
/// <param name="e">The event arguments.</param>
|
||||
public delegate void InAppNotificationClosedEventHandler(object sender, InAppNotificationClosedEventArgs e);
|
||||
|
||||
/// <summary>
|
||||
/// Provides data for the <see cref="InAppNotification"/> Dismissing event.
|
||||
/// </summary>
|
||||
public class InAppNotificationClosedEventArgs : EventArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="InAppNotificationClosedEventArgs"/> class.
|
||||
/// </summary>
|
||||
/// <param name="dismissKind">Dismiss kind that triggered the closing event</param>
|
||||
public InAppNotificationClosedEventArgs(InAppNotificationDismissKind dismissKind)
|
||||
{
|
||||
DismissKind = dismissKind;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the kind of action for the closing event.
|
||||
/// </summary>
|
||||
public InAppNotificationDismissKind DismissKind { get; private set; }
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,41 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
// Source: https://github.com/windows-toolkit/WindowsCommunityToolkit/tree/master/Microsoft.Toolkit.Uwp.UI.Controls/InAppNotification
|
||||
|
||||
namespace Notepads.Controls
|
||||
{
|
||||
using System;
|
||||
|
||||
/// <summary>
|
||||
/// A delegate for <see cref="InAppNotification"/> dismissing.
|
||||
/// </summary>
|
||||
/// <param name="sender">The sender.</param>
|
||||
/// <param name="e">The event arguments.</param>
|
||||
public delegate void InAppNotificationClosingEventHandler(object sender, InAppNotificationClosingEventArgs e);
|
||||
|
||||
/// <summary>
|
||||
/// Provides data for the <see cref="InAppNotification"/> Dismissing event.
|
||||
/// </summary>
|
||||
public class InAppNotificationClosingEventArgs : EventArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="InAppNotificationClosingEventArgs"/> class.
|
||||
/// </summary>
|
||||
/// <param name="dismissKind">Dismiss kind that triggered the closing event</param>
|
||||
public InAppNotificationClosingEventArgs(InAppNotificationDismissKind dismissKind)
|
||||
{
|
||||
DismissKind = dismissKind;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the kind of action for the closing event.
|
||||
/// </summary>
|
||||
public InAppNotificationDismissKind DismissKind { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether the notification should be closed.
|
||||
/// </summary>
|
||||
public bool Cancel { get; set; }
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,28 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
// Source: https://github.com/windows-toolkit/WindowsCommunityToolkit/tree/master/Microsoft.Toolkit.Uwp.UI.Controls/InAppNotification
|
||||
|
||||
namespace Notepads.Controls
|
||||
{
|
||||
/// <summary>
|
||||
/// Enumeration to describe how an InAppNotification was dismissed
|
||||
/// </summary>
|
||||
public enum InAppNotificationDismissKind
|
||||
{
|
||||
/// <summary>
|
||||
/// When the system dismissed the notification.
|
||||
/// </summary>
|
||||
Programmatic,
|
||||
|
||||
/// <summary>
|
||||
/// When user explicitly dismissed the notification.
|
||||
/// </summary>
|
||||
User,
|
||||
|
||||
/// <summary>
|
||||
/// When the system dismissed the notification after timeout.
|
||||
/// </summary>
|
||||
Timeout
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,34 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
// Source: https://github.com/windows-toolkit/WindowsCommunityToolkit/tree/master/Microsoft.Toolkit.Uwp.UI.Controls/InAppNotification
|
||||
|
||||
namespace Notepads.Controls
|
||||
{
|
||||
using System;
|
||||
|
||||
/// <summary>
|
||||
/// A delegate for <see cref="InAppNotification"/> opening.
|
||||
/// </summary>
|
||||
/// <param name="sender">The sender.</param>
|
||||
/// <param name="e">The event arguments.</param>
|
||||
public delegate void InAppNotificationOpeningEventHandler(object sender, InAppNotificationOpeningEventArgs e);
|
||||
|
||||
/// <summary>
|
||||
/// Provides data for the <see cref="InAppNotification"/> Dismissing event.
|
||||
/// </summary>
|
||||
public class InAppNotificationOpeningEventArgs : EventArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="InAppNotificationOpeningEventArgs"/> class.
|
||||
/// </summary>
|
||||
public InAppNotificationOpeningEventArgs()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether the notification should be opened.
|
||||
/// </summary>
|
||||
public bool Cancel { get; set; }
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,26 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
// Source: https://github.com/windows-toolkit/WindowsCommunityToolkit/tree/master/Microsoft.Toolkit.Uwp.UI.Controls/InAppNotification
|
||||
|
||||
namespace Notepads.Controls
|
||||
{
|
||||
using Windows.UI.Xaml;
|
||||
|
||||
/// <summary>
|
||||
/// Base class that contains options of notification
|
||||
/// </summary>
|
||||
internal class NotificationOptions
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets duration of the stacked notification
|
||||
/// </summary>
|
||||
public int Duration { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets Content of the notification
|
||||
/// Could be either a <see cref="string"/> or a <see cref="UIElement"/> or a <see cref="DataTemplate"/>
|
||||
/// </summary>
|
||||
public object Content { get; set; }
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,28 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
// Source: https://github.com/windows-toolkit/WindowsCommunityToolkit/tree/master/Microsoft.Toolkit.Uwp.UI.Controls/InAppNotification
|
||||
|
||||
namespace Notepads.Controls
|
||||
{
|
||||
/// <summary>
|
||||
/// The Stack mode of an in-app notification.
|
||||
/// </summary>
|
||||
public enum StackMode
|
||||
{
|
||||
/// <summary>
|
||||
/// Each notification will replace the previous one
|
||||
/// </summary>
|
||||
Replace,
|
||||
|
||||
/// <summary>
|
||||
/// Opening a notification will display it immediately, remaining notifications will appear when a notification is dismissed
|
||||
/// </summary>
|
||||
StackInFront,
|
||||
|
||||
/// <summary>
|
||||
/// Dismissing a notification will show the next one in the queue
|
||||
/// </summary>
|
||||
QueueBehind
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,200 @@
|
||||
<ResourceDictionary
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:local="using:Notepads.Controls">
|
||||
|
||||
<ResourceDictionary.ThemeDictionaries>
|
||||
<!-- Default is a fallback if a more precise theme isn't called
|
||||
out below -->
|
||||
<ResourceDictionary x:Key="Default">
|
||||
<SolidColorBrush x:Key="SystemControlMSEdgeNotificationPointerOverChromeBrush" Color="Transparent" />
|
||||
<SolidColorBrush x:Key="SystemControlMSEdgeNotificationPointerOverForegroundBrush" Color="{ThemeResource SystemColorButtonTextColor}"/>
|
||||
<SolidColorBrush x:Key="SystemControlMSEdgeNotificationButtonBorderBrush" Color="Transparent" />
|
||||
</ResourceDictionary>
|
||||
|
||||
<!-- HighContrast is used in all high contrast themes -->
|
||||
<ResourceDictionary x:Key="HighContrast">
|
||||
<SolidColorBrush x:Key="SystemControlMSEdgeNotificationPointerOverChromeBrush" Color="{ThemeResource SystemColorHighlightColor}" />
|
||||
<SolidColorBrush x:Key="SystemControlMSEdgeNotificationPointerOverForegroundBrush" Color="{ThemeResource SystemColorHighlightTextColor}"/>
|
||||
<SolidColorBrush x:Key="SystemControlMSEdgeNotificationButtonBorderBrush" Color="{ThemeResource SystemColorButtonTextColor}" />
|
||||
</ResourceDictionary>
|
||||
</ResourceDictionary.ThemeDictionaries>
|
||||
|
||||
<Style x:Key="DismissTextBlockButtonStyle" TargetType="ButtonBase">
|
||||
<Setter Property="Background" Value="{ThemeResource HyperlinkButtonBackground}" />
|
||||
<Setter Property="Foreground" Value="{ThemeResource ApplicationForegroundThemeBrush}" />
|
||||
<Setter Property="Width" Value="40" />
|
||||
<Setter Property="Height" Value="40" />
|
||||
<Setter Property="UseSystemFocusVisuals" Value="True" />
|
||||
<Setter Property="HighContrastAdjustment" Value="None" />
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="ButtonBase">
|
||||
<Grid x:Name="RootGrid" Margin="{TemplateBinding Padding}" Background="{TemplateBinding Background}">
|
||||
<Border x:Name="TextBorder" BorderThickness="2" BorderBrush="{ThemeResource SystemControlMSEdgeNotificationButtonBorderBrush}">
|
||||
<ContentPresenter x:Name="Text"
|
||||
Content="{TemplateBinding Content}"
|
||||
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
|
||||
VerticalAlignment="{TemplateBinding VerticalContentAlignment}" />
|
||||
</Border>
|
||||
<VisualStateManager.VisualStateGroups>
|
||||
<VisualStateGroup x:Name="CommonStates">
|
||||
<VisualState x:Name="Normal" />
|
||||
<VisualState x:Name="PointerOver">
|
||||
<Storyboard>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="Text" Storyboard.TargetProperty="Foreground">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SystemControlMSEdgeNotificationPointerOverForegroundBrush}" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="RootGrid" Storyboard.TargetProperty="Background">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SystemControlMSEdgeNotificationPointerOverChromeBrush}" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
</Storyboard>
|
||||
</VisualState>
|
||||
<VisualState x:Name="Pressed">
|
||||
<Storyboard>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="Text" Storyboard.TargetProperty="Foreground">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SystemControlMSEdgeNotificationPointerOverForegroundBrush}" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="RootGrid" Storyboard.TargetProperty="Background">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SystemControlMSEdgeNotificationPointerOverChromeBrush}" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
</Storyboard>
|
||||
</VisualState>
|
||||
<VisualState x:Name="Disabled">
|
||||
<Storyboard>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="Text" Storyboard.TargetProperty="Foreground">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource HyperlinkButtonForegroundDisabled}" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="Text" Storyboard.TargetProperty="Background">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource HyperlinkButtonBackgroundDisabled}" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="Text" Storyboard.TargetProperty="BorderBrush">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource HyperlinkButtonBorderBrushDisabled}" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
</Storyboard>
|
||||
</VisualState>
|
||||
</VisualStateGroup>
|
||||
</VisualStateManager.VisualStateGroups>
|
||||
</Grid>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<ControlTemplate x:Key="MSEdgeNotificationTemplate"
|
||||
TargetType="local:InAppNotification">
|
||||
<Grid>
|
||||
<VisualStateManager.VisualStateGroups>
|
||||
<VisualStateGroup x:Name="State">
|
||||
<VisualState x:Name="Collapsed">
|
||||
<Storyboard>
|
||||
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="RootGrid"
|
||||
Storyboard.TargetProperty="(UIElement.RenderTransform).(CompositeTransform.TranslateX)">
|
||||
<EasingDoubleKeyFrame KeyTime="0" Value="0" />
|
||||
<EasingDoubleKeyFrame local:InAppNotification.KeyFrameDuration="{Binding AnimationDuration, RelativeSource={RelativeSource TemplatedParent}}"
|
||||
Value="{Binding HorizontalOffset, RelativeSource={RelativeSource TemplatedParent}}" />
|
||||
</DoubleAnimationUsingKeyFrames>
|
||||
|
||||
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="RootGrid"
|
||||
Storyboard.TargetProperty="(UIElement.RenderTransform).(CompositeTransform.TranslateY)">
|
||||
<EasingDoubleKeyFrame KeyTime="0" Value="0" />
|
||||
<EasingDoubleKeyFrame local:InAppNotification.KeyFrameDuration="{Binding AnimationDuration, RelativeSource={RelativeSource TemplatedParent}}"
|
||||
Value="{Binding VerticalOffset, RelativeSource={RelativeSource TemplatedParent}}"/>
|
||||
</DoubleAnimationUsingKeyFrames>
|
||||
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="RootGrid"
|
||||
Storyboard.TargetProperty="(UIElement.Visibility)">
|
||||
<DiscreteObjectKeyFrame KeyTime="0">
|
||||
<DiscreteObjectKeyFrame.Value>
|
||||
<Visibility>Visible</Visibility>
|
||||
</DiscreteObjectKeyFrame.Value>
|
||||
</DiscreteObjectKeyFrame>
|
||||
<DiscreteObjectKeyFrame local:InAppNotification.KeyFrameDuration="{Binding AnimationDuration, RelativeSource={RelativeSource TemplatedParent}}">
|
||||
<DiscreteObjectKeyFrame.Value>
|
||||
<Visibility>Collapsed</Visibility>
|
||||
</DiscreteObjectKeyFrame.Value>
|
||||
</DiscreteObjectKeyFrame>
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
</Storyboard>
|
||||
</VisualState>
|
||||
|
||||
<VisualState x:Name="Visible">
|
||||
<Storyboard>
|
||||
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="RootGrid"
|
||||
Storyboard.TargetProperty="(UIElement.RenderTransform).(CompositeTransform.TranslateX)">
|
||||
<EasingDoubleKeyFrame KeyTime="0" Value="{Binding HorizontalOffset, RelativeSource={RelativeSource TemplatedParent}}" />
|
||||
<EasingDoubleKeyFrame local:InAppNotification.KeyFrameDuration="{Binding AnimationDuration, RelativeSource={RelativeSource TemplatedParent}}" Value="0" />
|
||||
</DoubleAnimationUsingKeyFrames>
|
||||
|
||||
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="RootGrid"
|
||||
Storyboard.TargetProperty="(UIElement.RenderTransform).(CompositeTransform.TranslateY)">
|
||||
<EasingDoubleKeyFrame KeyTime="0" Value="{Binding VerticalOffset, RelativeSource={RelativeSource TemplatedParent}}" />
|
||||
<EasingDoubleKeyFrame local:InAppNotification.KeyFrameDuration="{Binding AnimationDuration, RelativeSource={RelativeSource TemplatedParent}}" Value="0" />
|
||||
</DoubleAnimationUsingKeyFrames>
|
||||
</Storyboard>
|
||||
</VisualState>
|
||||
</VisualStateGroup>
|
||||
</VisualStateManager.VisualStateGroups>
|
||||
|
||||
<Grid x:Name="RootGrid"
|
||||
RenderTransformOrigin="{TemplateBinding RenderTransformOrigin}"
|
||||
Margin="{TemplateBinding Margin}"
|
||||
Padding="{TemplateBinding Padding}"
|
||||
MaxWidth="{TemplateBinding MaxWidth}"
|
||||
Visibility="{TemplateBinding Visibility}"
|
||||
Background="{TemplateBinding Background}"
|
||||
BorderBrush="{TemplateBinding BorderBrush}"
|
||||
BorderThickness="{TemplateBinding BorderThickness}">
|
||||
|
||||
<Grid.RenderTransform>
|
||||
<CompositeTransform />
|
||||
</Grid.RenderTransform>
|
||||
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<ContentPresenter HorizontalAlignment="{TemplateBinding HorizontalAlignment}"
|
||||
HorizontalContentAlignment="Stretch"
|
||||
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
|
||||
VerticalContentAlignment="Center"
|
||||
TextWrapping="WrapWholeWords" />
|
||||
|
||||
<Button x:Name="PART_DismissButton"
|
||||
Grid.Column="1"
|
||||
Margin="24,0,0,0"
|
||||
FontSize="16"
|
||||
Style="{StaticResource DismissTextBlockButtonStyle}"
|
||||
Content=""
|
||||
FontFamily="Segoe MDL2 Assets"
|
||||
AutomationProperties.Name="Dismiss">
|
||||
<Button.RenderTransform>
|
||||
<TranslateTransform x:Name="DismissButtonTransform" X="18" />
|
||||
</Button.RenderTransform>
|
||||
</Button>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</ControlTemplate>
|
||||
|
||||
<Style x:Key="MSEdgeNotificationStyle" TargetType="local:InAppNotification">
|
||||
<Setter Property="Background" Value="{ThemeResource SystemControlBackgroundChromeMediumBrush}" />
|
||||
<Setter Property="Foreground" Value="{ThemeResource SystemControlForegroundBaseHighBrush}" />
|
||||
<Setter Property="BorderBrush" Value="{ThemeResource SystemControlBackgroundBaseLowBrush}" />
|
||||
<Setter Property="Visibility" Value="Collapsed" />
|
||||
<Setter Property="BorderThickness" Value="2" />
|
||||
<Setter Property="HorizontalAlignment" Value="Stretch" />
|
||||
<Setter Property="VerticalAlignment" Value="Bottom" />
|
||||
<Setter Property="MinHeight" Value="55" />
|
||||
<Setter Property="FontSize" Value="16" />
|
||||
<Setter Property="RenderTransformOrigin" Value="0.5,1" />
|
||||
<Setter Property="Margin" Value="24,12" />
|
||||
<Setter Property="Padding" Value="24,12" />
|
||||
<Setter Property="MaxWidth" Value="960" />
|
||||
<Setter Property="MinWidth" Value="132" />
|
||||
<Setter Property="AnimationDuration" Value="0:0:0.100" />
|
||||
<Setter Property="VerticalOffset" Value="100" />
|
||||
<Setter Property="HorizontalOffset" Value="0" />
|
||||
<Setter Property="Template" Value="{StaticResource MSEdgeNotificationTemplate}" />
|
||||
</Style>
|
||||
</ResourceDictionary>
|
||||
@ -0,0 +1,47 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
// Source: https://github.com/windows-toolkit/WindowsCommunityToolkit/tree/master/Microsoft.Toolkit.Uwp.UI.Controls/MarkdownTextBlock
|
||||
|
||||
namespace Notepads.Controls
|
||||
{
|
||||
using System;
|
||||
using Windows.UI.Xaml.Documents;
|
||||
|
||||
/// <summary>
|
||||
/// Arguments for the <see cref="MarkdownTextBlock.CodeBlockResolving"/> event when a Code Block is being rendered.
|
||||
/// </summary>
|
||||
public class CodeBlockResolvingEventArgs : EventArgs
|
||||
{
|
||||
internal CodeBlockResolvingEventArgs(InlineCollection inlineCollection, string text, string codeLanguage)
|
||||
{
|
||||
InlineCollection = inlineCollection;
|
||||
Text = text;
|
||||
CodeLanguage = codeLanguage;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the language of the Code Block, as specified by ```{Language} on the first line of the block,
|
||||
/// e.g. <para/>
|
||||
/// ```C# <para/>
|
||||
/// public void Method();<para/>
|
||||
/// ```<para/>
|
||||
/// </summary>
|
||||
public string CodeLanguage { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the raw code block text
|
||||
/// </summary>
|
||||
public string Text { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets Collection to add formatted Text to.
|
||||
/// </summary>
|
||||
public InlineCollection InlineCollection { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether this event was handled successfully.
|
||||
/// </summary>
|
||||
public bool Handled { get; set; } = false;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,73 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
// Source: https://github.com/windows-toolkit/WindowsCommunityToolkit/tree/master/Microsoft.Toolkit.Uwp.UI.Controls/MarkdownTextBlock
|
||||
|
||||
namespace Notepads.Controls
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Windows.Foundation;
|
||||
using Windows.UI.Xaml.Media;
|
||||
|
||||
/// <summary>
|
||||
/// Arguments for the <see cref="MarkdownTextBlock.ImageResolving"/> event which is called when a url needs to be resolved to a <see cref="ImageSource"/>.
|
||||
/// </summary>
|
||||
public class ImageResolvingEventArgs : EventArgs
|
||||
{
|
||||
private readonly IList<TaskCompletionSource<object>> _deferrals;
|
||||
|
||||
internal ImageResolvingEventArgs(string url, string tooltip)
|
||||
{
|
||||
_deferrals = new List<TaskCompletionSource<object>>();
|
||||
Url = url;
|
||||
Tooltip = tooltip;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the url of the image in the markdown document.
|
||||
/// </summary>
|
||||
public string Url { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the tooltip of the image in the markdown document.
|
||||
/// </summary>
|
||||
public string Tooltip { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether this event was handled successfully.
|
||||
/// </summary>
|
||||
public bool Handled { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the image to display in the <see cref="MarkdownTextBlock"/>.
|
||||
/// </summary>
|
||||
public ImageSource Image { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Informs the <see cref="MarkdownTextBlock"/> that the event handler might run asynchronously.
|
||||
/// </summary>
|
||||
/// <returns>Deferral</returns>
|
||||
public Deferral GetDeferral()
|
||||
{
|
||||
var task = new TaskCompletionSource<object>();
|
||||
_deferrals.Add(task);
|
||||
|
||||
return new Deferral(() =>
|
||||
{
|
||||
task.SetResult(null);
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a <see cref="Task"/> that completes when all <see cref="Deferral"/>s have completed.
|
||||
/// </summary>
|
||||
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
|
||||
internal Task WaitForDeferrals()
|
||||
{
|
||||
return Task.WhenAll(_deferrals.Select(f => f.Task));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,25 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
// Source: https://github.com/windows-toolkit/WindowsCommunityToolkit/tree/master/Microsoft.Toolkit.Uwp.UI.Controls/MarkdownTextBlock
|
||||
|
||||
namespace Notepads.Controls
|
||||
{
|
||||
using System;
|
||||
|
||||
/// <summary>
|
||||
/// Arguments for the OnLinkClicked event which is fired then the user presses a link.
|
||||
/// </summary>
|
||||
public class LinkClickedEventArgs : EventArgs
|
||||
{
|
||||
internal LinkClickedEventArgs(string link)
|
||||
{
|
||||
Link = link;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the link that was tapped.
|
||||
/// </summary>
|
||||
public string Link { get; }
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,196 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
// Source: https://github.com/windows-toolkit/WindowsCommunityToolkit/tree/master/Microsoft.Toolkit.Parsers/Markdown/Blocks
|
||||
|
||||
namespace Notepads.Controls.Markdown
|
||||
{
|
||||
using System.Text;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a block of text that is displayed in a fixed-width font. Inline elements and
|
||||
/// escape sequences are ignored inside the code block.
|
||||
/// </summary>
|
||||
public class CodeBlock : MarkdownBlock
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="CodeBlock"/> class.
|
||||
/// </summary>
|
||||
public CodeBlock()
|
||||
: base(MarkdownBlockType.Code)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the source code to display.
|
||||
/// </summary>
|
||||
public string Text { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the Language specified in prefix, e.g. ```c# (Github Style Parsing).<para/>
|
||||
/// This does not guarantee that the Code Block has a language, or no language, some valid code might not have been prefixed, and this will still return null. <para/>
|
||||
/// To ensure all Code is Highlighted (If desired), you might have to determine the language from the provided string, such as looking for key words.
|
||||
/// </summary>
|
||||
public string CodeLanguage { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Parses a code block.
|
||||
/// </summary>
|
||||
/// <param name="markdown"> The markdown text. </param>
|
||||
/// <param name="start"> The location of the first character in the block. </param>
|
||||
/// <param name="maxEnd"> The location to stop parsing. </param>
|
||||
/// <param name="quoteDepth"> The current nesting level for block quoting. </param>
|
||||
/// <param name="actualEnd"> Set to the end of the block when the return value is non-null. </param>
|
||||
/// <returns> A parsed code block, or <c>null</c> if this is not a code block. </returns>
|
||||
internal static CodeBlock Parse(string markdown, int start, int maxEnd, int quoteDepth, out int actualEnd)
|
||||
{
|
||||
StringBuilder code = null;
|
||||
actualEnd = start;
|
||||
bool insideCodeBlock = false;
|
||||
string codeLanguage = string.Empty;
|
||||
|
||||
/*
|
||||
Two options here:
|
||||
Either every line starts with a tab character or at least 4 spaces
|
||||
Or the code block starts and ends with ```
|
||||
*/
|
||||
|
||||
foreach (var lineInfo in Common.ParseLines(markdown, start, maxEnd, quoteDepth))
|
||||
{
|
||||
int pos = lineInfo.StartOfLine;
|
||||
if (pos < maxEnd && markdown[pos] == '`')
|
||||
{
|
||||
var backTickCount = 0;
|
||||
while (pos < maxEnd && backTickCount < 3)
|
||||
{
|
||||
if (markdown[pos] == '`')
|
||||
{
|
||||
backTickCount++;
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
pos++;
|
||||
}
|
||||
|
||||
if (backTickCount == 3)
|
||||
{
|
||||
insideCodeBlock = !insideCodeBlock;
|
||||
|
||||
if (!insideCodeBlock)
|
||||
{
|
||||
actualEnd = lineInfo.StartOfNextLine;
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Collects the Programming Language from the end of the starting ticks.
|
||||
while (pos < lineInfo.EndOfLine)
|
||||
{
|
||||
codeLanguage += markdown[pos];
|
||||
pos++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!insideCodeBlock)
|
||||
{
|
||||
// Add every line that starts with a tab character or at least 4 spaces.
|
||||
if (pos < maxEnd && markdown[pos] == '\t')
|
||||
{
|
||||
pos++;
|
||||
}
|
||||
else
|
||||
{
|
||||
int spaceCount = 0;
|
||||
while (pos < maxEnd && spaceCount < 4)
|
||||
{
|
||||
if (markdown[pos] == ' ')
|
||||
{
|
||||
spaceCount++;
|
||||
}
|
||||
else if (markdown[pos] == '\t')
|
||||
{
|
||||
spaceCount += 4;
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
pos++;
|
||||
}
|
||||
|
||||
if (spaceCount < 4)
|
||||
{
|
||||
// We found a line that doesn't start with a tab or 4 spaces.
|
||||
// But don't end the code block until we find a non-blank line.
|
||||
if (lineInfo.IsLineBlank == false)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Separate each line of the code text.
|
||||
if (code == null)
|
||||
{
|
||||
code = new StringBuilder();
|
||||
}
|
||||
else
|
||||
{
|
||||
code.AppendLine();
|
||||
}
|
||||
|
||||
if (lineInfo.IsLineBlank == false)
|
||||
{
|
||||
// Append the code text, excluding the first tab/4 spaces, and convert tab characters into spaces.
|
||||
string lineText = markdown.Substring(pos, lineInfo.EndOfLine - pos);
|
||||
int startOfLinePos = code.Length;
|
||||
for (int i = 0; i < lineText.Length; i++)
|
||||
{
|
||||
char c = lineText[i];
|
||||
if (c == '\t')
|
||||
{
|
||||
code.Append(' ', 4 - ((code.Length - startOfLinePos) % 4));
|
||||
}
|
||||
else
|
||||
{
|
||||
code.Append(c);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Update the end position.
|
||||
actualEnd = lineInfo.StartOfNextLine;
|
||||
}
|
||||
|
||||
if (code == null)
|
||||
{
|
||||
// Not a valid code block.
|
||||
actualEnd = start;
|
||||
return null;
|
||||
}
|
||||
|
||||
// Blank lines should be trimmed from the start and end.
|
||||
return new CodeBlock()
|
||||
{
|
||||
Text = code.ToString().Trim('\r', '\n'),
|
||||
CodeLanguage = !string.IsNullOrWhiteSpace(codeLanguage) ? codeLanguage.Trim() : null
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts the object into it's textual representation.
|
||||
/// </summary>
|
||||
/// <returns> The textual representation of this object. </returns>
|
||||
public override string ToString()
|
||||
{
|
||||
return Text;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,166 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
// Source: https://github.com/windows-toolkit/WindowsCommunityToolkit/tree/master/Microsoft.Toolkit.Parsers/Markdown/Blocks
|
||||
|
||||
namespace Notepads.Controls.Markdown
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a heading.
|
||||
/// <seealso href="https://spec.commonmark.org/0.29/#atx-headings">Single-Line Header CommonMark Spec</seealso>
|
||||
/// <seealso href="https://spec.commonmark.org/0.29/#setext-headings">Two-Line Header CommonMark Spec</seealso>
|
||||
/// </summary>
|
||||
public class HeaderBlock : MarkdownBlock
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="HeaderBlock"/> class.
|
||||
/// </summary>
|
||||
public HeaderBlock()
|
||||
: base(MarkdownBlockType.Header)
|
||||
{
|
||||
}
|
||||
|
||||
private int _headerLevel;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the header level (1-6). 1 is the most important header, 6 is the least important.
|
||||
/// </summary>
|
||||
public int HeaderLevel
|
||||
{
|
||||
get => _headerLevel;
|
||||
|
||||
set
|
||||
{
|
||||
if (value < 1 || value > 6)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException("HeaderLevel", "The header level must be between 1 and 6 (inclusive).");
|
||||
}
|
||||
|
||||
_headerLevel = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the contents of the block.
|
||||
/// </summary>
|
||||
public IList<MarkdownInline> Inlines { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Parses a header that starts with a hash.
|
||||
/// </summary>
|
||||
/// <param name="markdown"> The markdown text. </param>
|
||||
/// <param name="start"> The location of the first hash character. </param>
|
||||
/// <param name="end"> The location of the end of the line. </param>
|
||||
/// <returns> A parsed header block, or <c>null</c> if this is not a header. </returns>
|
||||
internal static HeaderBlock ParseHashPrefixedHeader(string markdown, int start, int end)
|
||||
{
|
||||
// This type of header starts with one or more '#' characters, followed by the header
|
||||
// text, optionally followed by any number of hash characters.
|
||||
var result = new HeaderBlock();
|
||||
|
||||
// Figure out how many consecutive hash characters there are.
|
||||
int pos = start;
|
||||
while (pos < end && markdown[pos] == '#' && pos - start < 6)
|
||||
{
|
||||
pos++;
|
||||
}
|
||||
|
||||
result.HeaderLevel = pos - start;
|
||||
if (result.HeaderLevel == 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
// Ignore any hashes at the end of the line.
|
||||
while (pos < end && markdown[end - 1] == '#')
|
||||
{
|
||||
end--;
|
||||
}
|
||||
|
||||
// Parse the inline content.
|
||||
result.Inlines = Common.ParseInlineChildren(markdown, pos, end);
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parses a two-line header.
|
||||
/// </summary>
|
||||
/// <param name="markdown"> The markdown text. </param>
|
||||
/// <param name="firstLineStart"> The location of the start of the first line. </param>
|
||||
/// <param name="firstLineEnd"> The location of the end of the first line. </param>
|
||||
/// <param name="secondLineStart"> The location of the start of the second line. </param>
|
||||
/// <param name="secondLineEnd"> The location of the end of the second line. </param>
|
||||
/// <returns> A parsed header block, or <c>null</c> if this is not a header. </returns>
|
||||
internal static HeaderBlock ParseUnderlineStyleHeader(string markdown, int firstLineStart, int firstLineEnd, int secondLineStart, int secondLineEnd)
|
||||
{
|
||||
// This type of header starts with some text on the first line, followed by one or more
|
||||
// underline characters ('=' or '-') on the second line.
|
||||
// The second line can have whitespace after the underline characters, but not before
|
||||
// or between each character.
|
||||
|
||||
// Check the second line is valid.
|
||||
if (secondLineEnd <= secondLineStart)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
// Figure out what the underline character is ('=' or '-').
|
||||
char underlineChar = markdown[secondLineStart];
|
||||
if (underlineChar != '=' && underlineChar != '-')
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
// Read past consecutive underline characters.
|
||||
int pos = secondLineStart + 1;
|
||||
for (; pos < secondLineEnd; pos++)
|
||||
{
|
||||
char c = markdown[pos];
|
||||
if (c != underlineChar)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
pos++;
|
||||
}
|
||||
|
||||
// The rest of the line must be whitespace.
|
||||
for (; pos < secondLineEnd; pos++)
|
||||
{
|
||||
char c = markdown[pos];
|
||||
if (c != ' ' && c != '\t')
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
pos++;
|
||||
}
|
||||
|
||||
var result = new HeaderBlock
|
||||
{
|
||||
HeaderLevel = underlineChar == '=' ? 1 : 2,
|
||||
|
||||
// Parse the inline content.
|
||||
Inlines = Common.ParseInlineChildren(markdown, firstLineStart, firstLineEnd)
|
||||
};
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts the object into it's textual representation.
|
||||
/// </summary>
|
||||
/// <returns> The textual representation of this object. </returns>
|
||||
public override string ToString()
|
||||
{
|
||||
if (Inlines == null)
|
||||
{
|
||||
return base.ToString();
|
||||
}
|
||||
|
||||
return string.Join(string.Empty, Inlines);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,73 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
// Source: https://github.com/windows-toolkit/WindowsCommunityToolkit/tree/master/Microsoft.Toolkit.Parsers/Markdown/Blocks
|
||||
|
||||
namespace Notepads.Controls.Markdown
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a horizontal line.
|
||||
/// </summary>
|
||||
public class HorizontalRuleBlock : MarkdownBlock
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="HorizontalRuleBlock"/> class.
|
||||
/// </summary>
|
||||
public HorizontalRuleBlock()
|
||||
: base(MarkdownBlockType.HorizontalRule)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parses a horizontal rule.
|
||||
/// </summary>
|
||||
/// <param name="markdown"> The markdown text. </param>
|
||||
/// <param name="start"> The location of the start of the line. </param>
|
||||
/// <param name="end"> The location of the end of the line. </param>
|
||||
/// <returns> A parsed horizontal rule block, or <c>null</c> if this is not a horizontal rule. </returns>
|
||||
internal static HorizontalRuleBlock Parse(string markdown, int start, int end)
|
||||
{
|
||||
// A horizontal rule is a line with at least 3 stars, optionally separated by spaces
|
||||
// OR a line with at least 3 dashes, optionally separated by spaces
|
||||
// OR a line with at least 3 underscores, optionally separated by spaces.
|
||||
char hrChar = '\0';
|
||||
int hrCharCount = 0;
|
||||
int pos = start;
|
||||
while (pos < end)
|
||||
{
|
||||
char c = markdown[pos++];
|
||||
if (c == '*' || c == '-' || c == '_')
|
||||
{
|
||||
// All of the non-whitespace characters on the line must match.
|
||||
if (hrCharCount > 0 && c != hrChar)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
hrChar = c;
|
||||
hrCharCount++;
|
||||
}
|
||||
else if (c == '\n')
|
||||
{
|
||||
break;
|
||||
}
|
||||
else if (!ParseHelpers.IsMarkdownWhiteSpace(c))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// Hopefully there were at least 3 stars/dashes/underscores.
|
||||
return hrCharCount >= 3 ? new HorizontalRuleBlock() : null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts the object into it's textual representation.
|
||||
/// </summary>
|
||||
/// <returns> The textual representation of this object. </returns>
|
||||
public override string ToString()
|
||||
{
|
||||
return "---";
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,170 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
// Source: https://github.com/windows-toolkit/WindowsCommunityToolkit/tree/master/Microsoft.Toolkit.Parsers/Markdown/Blocks
|
||||
|
||||
namespace Notepads.Controls.Markdown
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents the target of a reference ([ref][]).
|
||||
/// </summary>
|
||||
public class LinkReferenceBlock : MarkdownBlock
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="LinkReferenceBlock"/> class.
|
||||
/// </summary>
|
||||
public LinkReferenceBlock()
|
||||
: base(MarkdownBlockType.LinkReference)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the reference ID.
|
||||
/// </summary>
|
||||
public string Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the link URL.
|
||||
/// </summary>
|
||||
public string Url { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a tooltip to display on hover.
|
||||
/// </summary>
|
||||
public string Tooltip { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to parse a reference e.g. "[example]: http://www.reddit.com 'title'".
|
||||
/// </summary>
|
||||
/// <param name="markdown"> The markdown text. </param>
|
||||
/// <param name="start"> The location to start parsing. </param>
|
||||
/// <param name="end"> The location to stop parsing. </param>
|
||||
/// <returns> A parsed markdown link, or <c>null</c> if this is not a markdown link. </returns>
|
||||
internal static LinkReferenceBlock Parse(string markdown, int start, int end)
|
||||
{
|
||||
// Expect a '[' character.
|
||||
if (start >= end || markdown[start] != '[')
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
// Find the ']' character
|
||||
int pos = start + 1;
|
||||
while (pos < end)
|
||||
{
|
||||
if (markdown[pos] == ']')
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
pos++;
|
||||
}
|
||||
|
||||
if (pos == end)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
// Extract the ID.
|
||||
string id = markdown.Substring(start + 1, pos - (start + 1));
|
||||
|
||||
// Expect the ':' character.
|
||||
pos++;
|
||||
if (pos == end || markdown[pos] != ':')
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
// Skip whitespace
|
||||
pos++;
|
||||
while (pos < end && ParseHelpers.IsMarkdownWhiteSpace(markdown[pos]))
|
||||
{
|
||||
pos++;
|
||||
}
|
||||
|
||||
if (pos == end)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
// Extract the URL.
|
||||
int urlStart = pos;
|
||||
while (pos < end && !ParseHelpers.IsMarkdownWhiteSpace(markdown[pos]))
|
||||
{
|
||||
pos++;
|
||||
}
|
||||
|
||||
string url = TextRunInline.ResolveEscapeSequences(markdown, urlStart, pos);
|
||||
|
||||
// Ignore leading '<' and trailing '>'.
|
||||
url = url.TrimStart('<').TrimEnd('>');
|
||||
|
||||
// Skip whitespace.
|
||||
pos++;
|
||||
while (pos < end && ParseHelpers.IsMarkdownWhiteSpace(markdown[pos]))
|
||||
{
|
||||
pos++;
|
||||
}
|
||||
|
||||
string tooltip = null;
|
||||
if (pos < end)
|
||||
{
|
||||
// Extract the tooltip.
|
||||
char tooltipEndChar;
|
||||
switch (markdown[pos])
|
||||
{
|
||||
case '(':
|
||||
tooltipEndChar = ')';
|
||||
break;
|
||||
|
||||
case '"':
|
||||
case '\'':
|
||||
tooltipEndChar = markdown[pos];
|
||||
break;
|
||||
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
|
||||
pos++;
|
||||
int tooltipStart = pos;
|
||||
while (pos < end && markdown[pos] != tooltipEndChar)
|
||||
{
|
||||
pos++;
|
||||
}
|
||||
|
||||
if (pos == end)
|
||||
{
|
||||
return null; // No end character.
|
||||
}
|
||||
|
||||
tooltip = markdown.Substring(tooltipStart, pos - tooltipStart);
|
||||
|
||||
// Check there isn't any trailing text.
|
||||
pos++;
|
||||
while (pos < end && ParseHelpers.IsMarkdownWhiteSpace(markdown[pos]))
|
||||
{
|
||||
pos++;
|
||||
}
|
||||
|
||||
if (pos < end)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// We found something!
|
||||
var result = new LinkReferenceBlock { Id = id, Url = url, Tooltip = tooltip };
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts the object into it's textual representation.
|
||||
/// </summary>
|
||||
/// <returns> The textual representation of this object. </returns>
|
||||
public override string ToString()
|
||||
{
|
||||
return $"[{Id}]: {Url} {Tooltip}";
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,24 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
// Source: https://github.com/windows-toolkit/WindowsCommunityToolkit/tree/master/Microsoft.Toolkit.Parsers/Markdown/Blocks/List
|
||||
|
||||
namespace Notepads.Controls.Markdown
|
||||
{
|
||||
using System.Collections.Generic;
|
||||
|
||||
/// <summary>
|
||||
/// This specifies the Content of the List element.
|
||||
/// </summary>
|
||||
public class ListItemBlock
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the contents of the list item.
|
||||
/// </summary>
|
||||
public IList<MarkdownBlock> Blocks { get; set; }
|
||||
|
||||
internal ListItemBlock()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,19 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
// Source: https://github.com/windows-toolkit/WindowsCommunityToolkit/tree/master/Microsoft.Toolkit.Parsers/Markdown/Blocks/List
|
||||
|
||||
namespace Notepads.Controls.Markdown
|
||||
{
|
||||
using System.Text;
|
||||
|
||||
internal class ListItemBuilder : MarkdownBlock
|
||||
{
|
||||
public StringBuilder Builder { get; } = new StringBuilder();
|
||||
|
||||
public ListItemBuilder()
|
||||
: base(MarkdownBlockType.ListItemBuilder)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,14 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
// Source: https://github.com/windows-toolkit/WindowsCommunityToolkit/tree/master/Microsoft.Toolkit.Parsers/Markdown/Blocks/List
|
||||
|
||||
namespace Notepads.Controls.Markdown
|
||||
{
|
||||
internal class ListItemPreamble
|
||||
{
|
||||
public ListStyle Style { get; set; }
|
||||
|
||||
public int ContentStartPos { get; set; }
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,15 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
// Source: https://github.com/windows-toolkit/WindowsCommunityToolkit/tree/master/Microsoft.Toolkit.Parsers/Markdown/Blocks/List
|
||||
|
||||
namespace Notepads.Controls.Markdown
|
||||
{
|
||||
internal class NestedListInfo
|
||||
{
|
||||
public ListBlock List { get; set; }
|
||||
|
||||
// The number of spaces at the start of the line the list first appeared.
|
||||
public int SpaceCount { get; set; }
|
||||
}
|
||||
}
|
||||