├── global.json
├── demo
├── ios
│ ├── Resources
│ │ ├── Default.png
│ │ ├── Default@2x.png
│ │ ├── Default-568h@2x.png
│ │ ├── Default-Portrait.png
│ │ └── Default-Portrait@2x.png
│ ├── Assets.xcassets
│ │ └── AppIcon.appiconset
│ │ │ ├── Icon120.png
│ │ │ ├── Icon152.png
│ │ │ ├── Icon167.png
│ │ │ ├── Icon180.png
│ │ │ ├── Icon20.png
│ │ │ ├── Icon29.png
│ │ │ ├── Icon40.png
│ │ │ ├── Icon58.png
│ │ │ ├── Icon60.png
│ │ │ ├── Icon76.png
│ │ │ ├── Icon80.png
│ │ │ ├── Icon87.png
│ │ │ ├── Icon1024.png
│ │ │ └── Contents.json
│ ├── Entitlements.plist
│ ├── Main.cs
│ ├── AppDelegate.cs
│ ├── Info.plist
│ └── Properties
│ │ └── AssemblyInfo.cs
├── app
│ ├── DIN Condensed Bold.ttf
│ ├── Font Awesome 5 Free-Solid-900.otf
│ ├── WebViewPage.cs
│ ├── GlobalUsings.cs
│ ├── AbsoluteLayoutPage.cs
│ ├── Demo.csproj
│ ├── Counter.cs
│ ├── FormattedStringPage.cs
│ ├── Shapes.cs
│ ├── EntryAndEditor.cs
│ ├── Timer.cs
│ ├── Brushes.cs
│ ├── BehaviorPage.cs
│ ├── SwipeViewPage.cs
│ ├── DynamicGrid.cs
│ ├── GroupedCollectionView.cs
│ └── DancingBars.cs
└── droid
│ ├── Resources
│ ├── mipmap-hdpi
│ │ ├── icon.png
│ │ └── launcher_foreground.png
│ ├── mipmap-mdpi
│ │ ├── icon.png
│ │ └── launcher_foreground.png
│ ├── mipmap-xhdpi
│ │ ├── icon.png
│ │ └── launcher_foreground.png
│ ├── mipmap-xxhdpi
│ │ ├── icon.png
│ │ └── launcher_foreground.png
│ ├── mipmap-xxxhdpi
│ │ ├── icon.png
│ │ └── launcher_foreground.png
│ ├── mipmap-anydpi-v26
│ │ ├── icon.xml
│ │ └── icon_round.xml
│ ├── values
│ │ ├── colors.xml
│ │ └── styles.xml
│ └── layout
│ │ ├── Toolbar.xml
│ │ └── Tabbar.xml
│ ├── Properties
│ ├── AndroidManifest.xml
│ └── AssemblyInfo.cs
│ └── MainActivity.cs
├── assets
└── flow-with-middleware.png
├── codegen
├── src
│ ├── attributes
│ │ ├── Interfaces.cs
│ │ ├── CodeGen.Attributes.csproj
│ │ └── Attributes.cs
│ ├── generators
│ │ └── CodeGen.Generators.csproj
│ └── metapackage
│ │ └── metapackage.csproj
├── pack
├── Directory.Build.props
├── test
│ ├── CodeGen.Tests.csproj
│ ├── SignalTests.cs
│ ├── RecordTests.cs
│ └── UnionTests.cs
├── README.md
└── CodeGen.sln
├── maps
├── demo
│ ├── ios
│ │ ├── Resources
│ │ │ ├── Default.png
│ │ │ ├── Default@2x.png
│ │ │ ├── Default-568h@2x.png
│ │ │ ├── Default-Portrait.png
│ │ │ └── Default-Portrait@2x.png
│ │ ├── Assets.xcassets
│ │ │ └── AppIcon.appiconset
│ │ │ │ ├── Icon120.png
│ │ │ │ ├── Icon152.png
│ │ │ │ ├── Icon167.png
│ │ │ │ ├── Icon180.png
│ │ │ │ ├── Icon20.png
│ │ │ │ ├── Icon29.png
│ │ │ │ ├── Icon40.png
│ │ │ │ ├── Icon58.png
│ │ │ │ ├── Icon60.png
│ │ │ │ ├── Icon76.png
│ │ │ │ ├── Icon80.png
│ │ │ │ ├── Icon87.png
│ │ │ │ ├── Icon1024.png
│ │ │ │ └── Contents.json
│ │ ├── Entitlements.plist
│ │ ├── Main.cs
│ │ ├── AppDelegate.cs
│ │ └── Info.plist
│ ├── app
│ │ ├── Font Awesome 5 Free-Solid-900.otf
│ │ ├── Maps.Demo.csproj
│ │ ├── SignalsStateReducers.cs
│ │ └── CityLoader.cs
│ └── droid
│ │ ├── Resources
│ │ ├── mipmap-hdpi
│ │ │ ├── icon.png
│ │ │ └── launcher_foreground.png
│ │ ├── mipmap-mdpi
│ │ │ ├── icon.png
│ │ │ └── launcher_foreground.png
│ │ ├── mipmap-xhdpi
│ │ │ ├── icon.png
│ │ │ └── launcher_foreground.png
│ │ ├── mipmap-xxhdpi
│ │ │ ├── icon.png
│ │ │ └── launcher_foreground.png
│ │ ├── mipmap-xxxhdpi
│ │ │ ├── icon.png
│ │ │ └── launcher_foreground.png
│ │ ├── mipmap-anydpi-v26
│ │ │ ├── icon.xml
│ │ │ └── icon_round.xml
│ │ ├── values
│ │ │ ├── colors.xml
│ │ │ └── styles.xml
│ │ └── layout
│ │ │ ├── Toolbar.xml
│ │ │ └── Tabbar.xml
│ │ ├── Properties
│ │ └── AndroidManifest.xml
│ │ └── MainActivity.cs
├── src
│ ├── MapElement.cs
│ ├── Laconic.Maps.csproj
│ ├── Polygon.cs
│ ├── Pin.cs
│ ├── Position.cs
│ ├── Map.cs
│ ├── Distance.cs
│ └── MapSpan.cs
└── test
│ ├── MapTests.csproj
│ └── MapTests.cs
├── cli-template
├── src
│ ├── ios
│ │ ├── Resources
│ │ │ ├── Default.png
│ │ │ ├── Default@2x.png
│ │ │ ├── Default-568h@2x.png
│ │ │ ├── Default-Portrait.png
│ │ │ ├── Default-Portrait@2x.png
│ │ │ └── LaunchScreen.storyboard
│ │ ├── Assets.xcassets
│ │ │ └── AppIcon.appiconset
│ │ │ │ ├── Icon1024.png
│ │ │ │ ├── Icon120.png
│ │ │ │ ├── Icon152.png
│ │ │ │ ├── Icon167.png
│ │ │ │ ├── Icon180.png
│ │ │ │ ├── Icon20.png
│ │ │ │ ├── Icon29.png
│ │ │ │ ├── Icon40.png
│ │ │ │ ├── Icon58.png
│ │ │ │ ├── Icon60.png
│ │ │ │ ├── Icon76.png
│ │ │ │ ├── Icon80.png
│ │ │ │ ├── Icon87.png
│ │ │ │ └── Contents.json
│ │ ├── Entitlements.plist
│ │ ├── AppDelegate.cs
│ │ └── Info.plist
│ ├── droid
│ │ ├── Resources
│ │ │ ├── mipmap-hdpi
│ │ │ │ ├── icon.png
│ │ │ │ └── launcher_foreground.png
│ │ │ ├── mipmap-mdpi
│ │ │ │ ├── icon.png
│ │ │ │ └── launcher_foreground.png
│ │ │ ├── mipmap-xhdpi
│ │ │ │ ├── icon.png
│ │ │ │ └── launcher_foreground.png
│ │ │ ├── mipmap-xxhdpi
│ │ │ │ ├── icon.png
│ │ │ │ └── launcher_foreground.png
│ │ │ ├── mipmap-xxxhdpi
│ │ │ │ ├── icon.png
│ │ │ │ └── launcher_foreground.png
│ │ │ └── layout
│ │ │ │ ├── Toolbar.xml
│ │ │ │ └── Tabbar.xml
│ │ ├── Properties
│ │ │ └── AndroidManifest.xml
│ │ └── MainActivity.cs
│ ├── Laconic.Template.sln.DotSettings
│ ├── app
│ │ ├── Laconic.Template.csproj
│ │ └── App.cs
│ └── .template.config
│ │ └── template.json
└── Package.csproj
├── src
├── GlobalUsings.cs
├── Laconic.csproj
├── View.cs
├── EventInfo.cs
├── GestureRecognizers.cs
├── Signal.cs
├── AbsoluteLayoutDiff.cs
├── ToViewListExtensions.cs
├── VisualElement.cs
├── FormattedString.cs
├── TabbedPage.cs
├── Behaviors.cs
├── ImageSource.cs
├── GridViewList.cs
├── Key.cs
├── Pages.cs
├── GridDiff.cs
├── Size.cs
├── Thickness.cs
├── Point.cs
├── ElementListDiff.cs
├── CornerRadius.cs
├── ViewList.cs
├── ItemsViews.cs
├── Controls.cs
├── ElementList.cs
├── DiffOperations.cs
├── ContextExpander.cs
└── ViewListDiff.cs
├── test
├── GlobalUsings.cs
├── PickerTests.cs
├── LaconicTests.csproj
├── StackLayoutTests.cs
├── ImageSourceTests.cs
├── ShapeTests.cs
├── BasicTests.cs
├── MiddlewareTests.cs
├── GridTests.cs
├── BehaviorTests.cs
├── CollectionViewTests.cs
└── BinderTests.cs
├── .gitignore
├── tools
└── codegen-controls
│ └── CodeGen.Controls.csproj
├── Directory.build.props
├── Laconic.sln.DotSettings
├── LICENSE
└── binding-report.md
/global.json:
--------------------------------------------------------------------------------
1 | {
2 | "sdk": {
3 | "version": "6.0.202"
4 | }
5 | }
--------------------------------------------------------------------------------
/demo/ios/Resources/Default.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shirshov/laconic/HEAD/demo/ios/Resources/Default.png
--------------------------------------------------------------------------------
/assets/flow-with-middleware.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shirshov/laconic/HEAD/assets/flow-with-middleware.png
--------------------------------------------------------------------------------
/codegen/src/attributes/Interfaces.cs:
--------------------------------------------------------------------------------
1 | namespace Laconic.CodeGeneration
2 | {
3 | public interface record {}
4 | }
--------------------------------------------------------------------------------
/demo/app/DIN Condensed Bold.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shirshov/laconic/HEAD/demo/app/DIN Condensed Bold.ttf
--------------------------------------------------------------------------------
/demo/ios/Resources/Default@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shirshov/laconic/HEAD/demo/ios/Resources/Default@2x.png
--------------------------------------------------------------------------------
/maps/demo/ios/Resources/Default.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shirshov/laconic/HEAD/maps/demo/ios/Resources/Default.png
--------------------------------------------------------------------------------
/demo/ios/Resources/Default-568h@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shirshov/laconic/HEAD/demo/ios/Resources/Default-568h@2x.png
--------------------------------------------------------------------------------
/maps/demo/ios/Resources/Default@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shirshov/laconic/HEAD/maps/demo/ios/Resources/Default@2x.png
--------------------------------------------------------------------------------
/demo/ios/Resources/Default-Portrait.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shirshov/laconic/HEAD/demo/ios/Resources/Default-Portrait.png
--------------------------------------------------------------------------------
/cli-template/src/ios/Resources/Default.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shirshov/laconic/HEAD/cli-template/src/ios/Resources/Default.png
--------------------------------------------------------------------------------
/demo/app/Font Awesome 5 Free-Solid-900.otf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shirshov/laconic/HEAD/demo/app/Font Awesome 5 Free-Solid-900.otf
--------------------------------------------------------------------------------
/demo/droid/Resources/mipmap-hdpi/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shirshov/laconic/HEAD/demo/droid/Resources/mipmap-hdpi/icon.png
--------------------------------------------------------------------------------
/demo/droid/Resources/mipmap-mdpi/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shirshov/laconic/HEAD/demo/droid/Resources/mipmap-mdpi/icon.png
--------------------------------------------------------------------------------
/demo/droid/Resources/mipmap-xhdpi/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shirshov/laconic/HEAD/demo/droid/Resources/mipmap-xhdpi/icon.png
--------------------------------------------------------------------------------
/demo/droid/Resources/mipmap-xxhdpi/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shirshov/laconic/HEAD/demo/droid/Resources/mipmap-xxhdpi/icon.png
--------------------------------------------------------------------------------
/demo/ios/Resources/Default-Portrait@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shirshov/laconic/HEAD/demo/ios/Resources/Default-Portrait@2x.png
--------------------------------------------------------------------------------
/maps/demo/ios/Resources/Default-568h@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shirshov/laconic/HEAD/maps/demo/ios/Resources/Default-568h@2x.png
--------------------------------------------------------------------------------
/cli-template/src/ios/Resources/Default@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shirshov/laconic/HEAD/cli-template/src/ios/Resources/Default@2x.png
--------------------------------------------------------------------------------
/demo/droid/Resources/mipmap-xxxhdpi/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shirshov/laconic/HEAD/demo/droid/Resources/mipmap-xxxhdpi/icon.png
--------------------------------------------------------------------------------
/maps/demo/ios/Resources/Default-Portrait.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shirshov/laconic/HEAD/maps/demo/ios/Resources/Default-Portrait.png
--------------------------------------------------------------------------------
/maps/demo/app/Font Awesome 5 Free-Solid-900.otf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shirshov/laconic/HEAD/maps/demo/app/Font Awesome 5 Free-Solid-900.otf
--------------------------------------------------------------------------------
/maps/demo/droid/Resources/mipmap-hdpi/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shirshov/laconic/HEAD/maps/demo/droid/Resources/mipmap-hdpi/icon.png
--------------------------------------------------------------------------------
/maps/demo/droid/Resources/mipmap-mdpi/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shirshov/laconic/HEAD/maps/demo/droid/Resources/mipmap-mdpi/icon.png
--------------------------------------------------------------------------------
/maps/demo/droid/Resources/mipmap-xhdpi/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shirshov/laconic/HEAD/maps/demo/droid/Resources/mipmap-xhdpi/icon.png
--------------------------------------------------------------------------------
/maps/demo/droid/Resources/mipmap-xxhdpi/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shirshov/laconic/HEAD/maps/demo/droid/Resources/mipmap-xxhdpi/icon.png
--------------------------------------------------------------------------------
/maps/demo/ios/Resources/Default-Portrait@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shirshov/laconic/HEAD/maps/demo/ios/Resources/Default-Portrait@2x.png
--------------------------------------------------------------------------------
/cli-template/src/ios/Resources/Default-568h@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shirshov/laconic/HEAD/cli-template/src/ios/Resources/Default-568h@2x.png
--------------------------------------------------------------------------------
/maps/demo/droid/Resources/mipmap-xxxhdpi/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shirshov/laconic/HEAD/maps/demo/droid/Resources/mipmap-xxxhdpi/icon.png
--------------------------------------------------------------------------------
/cli-template/src/droid/Resources/mipmap-hdpi/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shirshov/laconic/HEAD/cli-template/src/droid/Resources/mipmap-hdpi/icon.png
--------------------------------------------------------------------------------
/cli-template/src/droid/Resources/mipmap-mdpi/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shirshov/laconic/HEAD/cli-template/src/droid/Resources/mipmap-mdpi/icon.png
--------------------------------------------------------------------------------
/cli-template/src/ios/Resources/Default-Portrait.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shirshov/laconic/HEAD/cli-template/src/ios/Resources/Default-Portrait.png
--------------------------------------------------------------------------------
/cli-template/src/droid/Resources/mipmap-xhdpi/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shirshov/laconic/HEAD/cli-template/src/droid/Resources/mipmap-xhdpi/icon.png
--------------------------------------------------------------------------------
/cli-template/src/droid/Resources/mipmap-xxhdpi/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shirshov/laconic/HEAD/cli-template/src/droid/Resources/mipmap-xxhdpi/icon.png
--------------------------------------------------------------------------------
/cli-template/src/ios/Resources/Default-Portrait@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shirshov/laconic/HEAD/cli-template/src/ios/Resources/Default-Portrait@2x.png
--------------------------------------------------------------------------------
/demo/ios/Assets.xcassets/AppIcon.appiconset/Icon120.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shirshov/laconic/HEAD/demo/ios/Assets.xcassets/AppIcon.appiconset/Icon120.png
--------------------------------------------------------------------------------
/demo/ios/Assets.xcassets/AppIcon.appiconset/Icon152.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shirshov/laconic/HEAD/demo/ios/Assets.xcassets/AppIcon.appiconset/Icon152.png
--------------------------------------------------------------------------------
/demo/ios/Assets.xcassets/AppIcon.appiconset/Icon167.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shirshov/laconic/HEAD/demo/ios/Assets.xcassets/AppIcon.appiconset/Icon167.png
--------------------------------------------------------------------------------
/demo/ios/Assets.xcassets/AppIcon.appiconset/Icon180.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shirshov/laconic/HEAD/demo/ios/Assets.xcassets/AppIcon.appiconset/Icon180.png
--------------------------------------------------------------------------------
/demo/ios/Assets.xcassets/AppIcon.appiconset/Icon20.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shirshov/laconic/HEAD/demo/ios/Assets.xcassets/AppIcon.appiconset/Icon20.png
--------------------------------------------------------------------------------
/demo/ios/Assets.xcassets/AppIcon.appiconset/Icon29.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shirshov/laconic/HEAD/demo/ios/Assets.xcassets/AppIcon.appiconset/Icon29.png
--------------------------------------------------------------------------------
/demo/ios/Assets.xcassets/AppIcon.appiconset/Icon40.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shirshov/laconic/HEAD/demo/ios/Assets.xcassets/AppIcon.appiconset/Icon40.png
--------------------------------------------------------------------------------
/demo/ios/Assets.xcassets/AppIcon.appiconset/Icon58.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shirshov/laconic/HEAD/demo/ios/Assets.xcassets/AppIcon.appiconset/Icon58.png
--------------------------------------------------------------------------------
/demo/ios/Assets.xcassets/AppIcon.appiconset/Icon60.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shirshov/laconic/HEAD/demo/ios/Assets.xcassets/AppIcon.appiconset/Icon60.png
--------------------------------------------------------------------------------
/demo/ios/Assets.xcassets/AppIcon.appiconset/Icon76.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shirshov/laconic/HEAD/demo/ios/Assets.xcassets/AppIcon.appiconset/Icon76.png
--------------------------------------------------------------------------------
/demo/ios/Assets.xcassets/AppIcon.appiconset/Icon80.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shirshov/laconic/HEAD/demo/ios/Assets.xcassets/AppIcon.appiconset/Icon80.png
--------------------------------------------------------------------------------
/demo/ios/Assets.xcassets/AppIcon.appiconset/Icon87.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shirshov/laconic/HEAD/demo/ios/Assets.xcassets/AppIcon.appiconset/Icon87.png
--------------------------------------------------------------------------------
/cli-template/src/droid/Resources/mipmap-xxxhdpi/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shirshov/laconic/HEAD/cli-template/src/droid/Resources/mipmap-xxxhdpi/icon.png
--------------------------------------------------------------------------------
/demo/droid/Resources/mipmap-hdpi/launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shirshov/laconic/HEAD/demo/droid/Resources/mipmap-hdpi/launcher_foreground.png
--------------------------------------------------------------------------------
/demo/droid/Resources/mipmap-mdpi/launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shirshov/laconic/HEAD/demo/droid/Resources/mipmap-mdpi/launcher_foreground.png
--------------------------------------------------------------------------------
/demo/droid/Resources/mipmap-xhdpi/launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shirshov/laconic/HEAD/demo/droid/Resources/mipmap-xhdpi/launcher_foreground.png
--------------------------------------------------------------------------------
/demo/droid/Resources/mipmap-xxhdpi/launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shirshov/laconic/HEAD/demo/droid/Resources/mipmap-xxhdpi/launcher_foreground.png
--------------------------------------------------------------------------------
/demo/ios/Assets.xcassets/AppIcon.appiconset/Icon1024.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shirshov/laconic/HEAD/demo/ios/Assets.xcassets/AppIcon.appiconset/Icon1024.png
--------------------------------------------------------------------------------
/codegen/pack:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | dotnet pack src/generators -o ./packages
4 | dotnet pack src/attributes -o ./packages
5 | dotnet pack src/metapackage -o ./packages
6 |
--------------------------------------------------------------------------------
/demo/droid/Resources/mipmap-xxxhdpi/launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shirshov/laconic/HEAD/demo/droid/Resources/mipmap-xxxhdpi/launcher_foreground.png
--------------------------------------------------------------------------------
/maps/demo/ios/Assets.xcassets/AppIcon.appiconset/Icon120.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shirshov/laconic/HEAD/maps/demo/ios/Assets.xcassets/AppIcon.appiconset/Icon120.png
--------------------------------------------------------------------------------
/maps/demo/ios/Assets.xcassets/AppIcon.appiconset/Icon152.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shirshov/laconic/HEAD/maps/demo/ios/Assets.xcassets/AppIcon.appiconset/Icon152.png
--------------------------------------------------------------------------------
/maps/demo/ios/Assets.xcassets/AppIcon.appiconset/Icon167.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shirshov/laconic/HEAD/maps/demo/ios/Assets.xcassets/AppIcon.appiconset/Icon167.png
--------------------------------------------------------------------------------
/maps/demo/ios/Assets.xcassets/AppIcon.appiconset/Icon180.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shirshov/laconic/HEAD/maps/demo/ios/Assets.xcassets/AppIcon.appiconset/Icon180.png
--------------------------------------------------------------------------------
/maps/demo/ios/Assets.xcassets/AppIcon.appiconset/Icon20.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shirshov/laconic/HEAD/maps/demo/ios/Assets.xcassets/AppIcon.appiconset/Icon20.png
--------------------------------------------------------------------------------
/maps/demo/ios/Assets.xcassets/AppIcon.appiconset/Icon29.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shirshov/laconic/HEAD/maps/demo/ios/Assets.xcassets/AppIcon.appiconset/Icon29.png
--------------------------------------------------------------------------------
/maps/demo/ios/Assets.xcassets/AppIcon.appiconset/Icon40.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shirshov/laconic/HEAD/maps/demo/ios/Assets.xcassets/AppIcon.appiconset/Icon40.png
--------------------------------------------------------------------------------
/maps/demo/ios/Assets.xcassets/AppIcon.appiconset/Icon58.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shirshov/laconic/HEAD/maps/demo/ios/Assets.xcassets/AppIcon.appiconset/Icon58.png
--------------------------------------------------------------------------------
/maps/demo/ios/Assets.xcassets/AppIcon.appiconset/Icon60.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shirshov/laconic/HEAD/maps/demo/ios/Assets.xcassets/AppIcon.appiconset/Icon60.png
--------------------------------------------------------------------------------
/maps/demo/ios/Assets.xcassets/AppIcon.appiconset/Icon76.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shirshov/laconic/HEAD/maps/demo/ios/Assets.xcassets/AppIcon.appiconset/Icon76.png
--------------------------------------------------------------------------------
/maps/demo/ios/Assets.xcassets/AppIcon.appiconset/Icon80.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shirshov/laconic/HEAD/maps/demo/ios/Assets.xcassets/AppIcon.appiconset/Icon80.png
--------------------------------------------------------------------------------
/maps/demo/ios/Assets.xcassets/AppIcon.appiconset/Icon87.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shirshov/laconic/HEAD/maps/demo/ios/Assets.xcassets/AppIcon.appiconset/Icon87.png
--------------------------------------------------------------------------------
/demo/app/WebViewPage.cs:
--------------------------------------------------------------------------------
1 | namespace Laconic.Demo;
2 |
3 | static class WebViewPage
4 | {
5 | public static WebView Content() => new() { Source = "https://google.com" };
6 | }
--------------------------------------------------------------------------------
/maps/demo/droid/Resources/mipmap-hdpi/launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shirshov/laconic/HEAD/maps/demo/droid/Resources/mipmap-hdpi/launcher_foreground.png
--------------------------------------------------------------------------------
/maps/demo/droid/Resources/mipmap-mdpi/launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shirshov/laconic/HEAD/maps/demo/droid/Resources/mipmap-mdpi/launcher_foreground.png
--------------------------------------------------------------------------------
/maps/demo/droid/Resources/mipmap-xhdpi/launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shirshov/laconic/HEAD/maps/demo/droid/Resources/mipmap-xhdpi/launcher_foreground.png
--------------------------------------------------------------------------------
/maps/demo/droid/Resources/mipmap-xxhdpi/launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shirshov/laconic/HEAD/maps/demo/droid/Resources/mipmap-xxhdpi/launcher_foreground.png
--------------------------------------------------------------------------------
/maps/demo/ios/Assets.xcassets/AppIcon.appiconset/Icon1024.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shirshov/laconic/HEAD/maps/demo/ios/Assets.xcassets/AppIcon.appiconset/Icon1024.png
--------------------------------------------------------------------------------
/maps/demo/droid/Resources/mipmap-xxxhdpi/launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shirshov/laconic/HEAD/maps/demo/droid/Resources/mipmap-xxxhdpi/launcher_foreground.png
--------------------------------------------------------------------------------
/cli-template/src/droid/Resources/mipmap-hdpi/launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shirshov/laconic/HEAD/cli-template/src/droid/Resources/mipmap-hdpi/launcher_foreground.png
--------------------------------------------------------------------------------
/cli-template/src/droid/Resources/mipmap-mdpi/launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shirshov/laconic/HEAD/cli-template/src/droid/Resources/mipmap-mdpi/launcher_foreground.png
--------------------------------------------------------------------------------
/cli-template/src/ios/Assets.xcassets/AppIcon.appiconset/Icon1024.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shirshov/laconic/HEAD/cli-template/src/ios/Assets.xcassets/AppIcon.appiconset/Icon1024.png
--------------------------------------------------------------------------------
/cli-template/src/ios/Assets.xcassets/AppIcon.appiconset/Icon120.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shirshov/laconic/HEAD/cli-template/src/ios/Assets.xcassets/AppIcon.appiconset/Icon120.png
--------------------------------------------------------------------------------
/cli-template/src/ios/Assets.xcassets/AppIcon.appiconset/Icon152.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shirshov/laconic/HEAD/cli-template/src/ios/Assets.xcassets/AppIcon.appiconset/Icon152.png
--------------------------------------------------------------------------------
/cli-template/src/ios/Assets.xcassets/AppIcon.appiconset/Icon167.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shirshov/laconic/HEAD/cli-template/src/ios/Assets.xcassets/AppIcon.appiconset/Icon167.png
--------------------------------------------------------------------------------
/cli-template/src/ios/Assets.xcassets/AppIcon.appiconset/Icon180.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shirshov/laconic/HEAD/cli-template/src/ios/Assets.xcassets/AppIcon.appiconset/Icon180.png
--------------------------------------------------------------------------------
/cli-template/src/ios/Assets.xcassets/AppIcon.appiconset/Icon20.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shirshov/laconic/HEAD/cli-template/src/ios/Assets.xcassets/AppIcon.appiconset/Icon20.png
--------------------------------------------------------------------------------
/cli-template/src/ios/Assets.xcassets/AppIcon.appiconset/Icon29.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shirshov/laconic/HEAD/cli-template/src/ios/Assets.xcassets/AppIcon.appiconset/Icon29.png
--------------------------------------------------------------------------------
/cli-template/src/ios/Assets.xcassets/AppIcon.appiconset/Icon40.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shirshov/laconic/HEAD/cli-template/src/ios/Assets.xcassets/AppIcon.appiconset/Icon40.png
--------------------------------------------------------------------------------
/cli-template/src/ios/Assets.xcassets/AppIcon.appiconset/Icon58.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shirshov/laconic/HEAD/cli-template/src/ios/Assets.xcassets/AppIcon.appiconset/Icon58.png
--------------------------------------------------------------------------------
/cli-template/src/ios/Assets.xcassets/AppIcon.appiconset/Icon60.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shirshov/laconic/HEAD/cli-template/src/ios/Assets.xcassets/AppIcon.appiconset/Icon60.png
--------------------------------------------------------------------------------
/cli-template/src/ios/Assets.xcassets/AppIcon.appiconset/Icon76.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shirshov/laconic/HEAD/cli-template/src/ios/Assets.xcassets/AppIcon.appiconset/Icon76.png
--------------------------------------------------------------------------------
/cli-template/src/ios/Assets.xcassets/AppIcon.appiconset/Icon80.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shirshov/laconic/HEAD/cli-template/src/ios/Assets.xcassets/AppIcon.appiconset/Icon80.png
--------------------------------------------------------------------------------
/cli-template/src/ios/Assets.xcassets/AppIcon.appiconset/Icon87.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shirshov/laconic/HEAD/cli-template/src/ios/Assets.xcassets/AppIcon.appiconset/Icon87.png
--------------------------------------------------------------------------------
/cli-template/src/droid/Resources/mipmap-xhdpi/launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shirshov/laconic/HEAD/cli-template/src/droid/Resources/mipmap-xhdpi/launcher_foreground.png
--------------------------------------------------------------------------------
/cli-template/src/droid/Resources/mipmap-xxhdpi/launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shirshov/laconic/HEAD/cli-template/src/droid/Resources/mipmap-xxhdpi/launcher_foreground.png
--------------------------------------------------------------------------------
/demo/app/GlobalUsings.cs:
--------------------------------------------------------------------------------
1 | // Global using directives
2 |
3 | global using System;
4 | global using System.Collections.Generic;
5 | global using System.Linq;
6 | global using xf = Xamarin.Forms;
--------------------------------------------------------------------------------
/src/GlobalUsings.cs:
--------------------------------------------------------------------------------
1 | global using System;
2 | global using System.Collections;
3 | global using System.Collections.Generic;
4 | global using System.Linq;
5 | global using xf = Xamarin.Forms;
6 |
--------------------------------------------------------------------------------
/cli-template/src/droid/Resources/mipmap-xxxhdpi/launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shirshov/laconic/HEAD/cli-template/src/droid/Resources/mipmap-xxxhdpi/launcher_foreground.png
--------------------------------------------------------------------------------
/test/GlobalUsings.cs:
--------------------------------------------------------------------------------
1 | // Global using directives
2 |
3 | global using System;
4 | global using System.Collections.Generic;
5 | global using System.Linq;
6 | global using Shouldly;
7 | global using Xunit;
8 | global using xf = Xamarin.Forms;
--------------------------------------------------------------------------------
/demo/ios/Entitlements.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/maps/demo/ios/Entitlements.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/cli-template/src/ios/Entitlements.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | bin
2 | obj
3 | pkg
4 | packages
5 | artifacts
6 | redist
7 | .DS_Store
8 | .vs
9 | .idea
10 | xcuserdata
11 | Resource.Designer.cs
12 | *.apk
13 | *.nupkg
14 | *.pidb
15 | *.userprefs
16 | *.suo
17 | *.user
18 | *.targets.overrides
19 | *.orig
20 | .fake
21 | .ionide
--------------------------------------------------------------------------------
/demo/droid/Resources/mipmap-anydpi-v26/icon.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
8 |
--------------------------------------------------------------------------------
/demo/droid/Resources/mipmap-anydpi-v26/icon_round.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
8 |
--------------------------------------------------------------------------------
/maps/demo/droid/Resources/mipmap-anydpi-v26/icon.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
8 |
--------------------------------------------------------------------------------
/maps/demo/droid/Resources/mipmap-anydpi-v26/icon_round.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
8 |
--------------------------------------------------------------------------------
/demo/droid/Resources/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #FFFFFF
5 | #3F51B5
7 | #303F9F
9 | #FF4081
11 |
--------------------------------------------------------------------------------
/maps/demo/droid/Resources/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #FFFFFF
5 | #3F51B5
7 | #303F9F
9 | #FF4081
11 |
--------------------------------------------------------------------------------
/cli-template/src/droid/Resources/layout/Toolbar.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/codegen/src/generators/CodeGen.Generators.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | netcoreapp2.1
5 | Laconic.CodeGen.Generators
6 | Laconic.CodeGen.Generators
7 | 8.0
8 |
9 |
10 |
--------------------------------------------------------------------------------
/tools/codegen-controls/CodeGen.Controls.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 | Exe
4 | net6.0
5 | 10
6 | Laconic.CodeGen
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/src/Laconic.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | netstandard2.1
5 | 10
6 | enable
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/demo/droid/Properties/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/maps/demo/droid/Properties/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/demo/ios/Main.cs:
--------------------------------------------------------------------------------
1 | using UIKit;
2 |
3 | namespace Laconic.Demo
4 | {
5 | public class Application
6 | {
7 | // This is the main entry point of the application.
8 | static void Main(string[] args)
9 | {
10 | // if you want to use a different Application Delegate class from "AppDelegate"
11 | // you can specify it here.
12 | UIApplication.Main(args, null, "AppDelegate");
13 | }
14 | }
15 | }
--------------------------------------------------------------------------------
/maps/demo/ios/Main.cs:
--------------------------------------------------------------------------------
1 | using UIKit;
2 |
3 | namespace Laconic.Demo
4 | {
5 | public class Application
6 | {
7 | // This is the main entry point of the application.
8 | static void Main(string[] args)
9 | {
10 | // if you want to use a different Application Delegate class from "AppDelegate"
11 | // you can specify it here.
12 | UIApplication.Main(args, null, "AppDelegate");
13 | }
14 | }
15 | }
--------------------------------------------------------------------------------
/demo/droid/Resources/layout/Toolbar.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/maps/demo/droid/Resources/layout/Toolbar.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/codegen/src/attributes/CodeGen.Attributes.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | netstandard2.0
5 | 8.0
6 | Laconic.CodeGen.Attributes
7 | Laconic.CodeGen.Attributes
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/cli-template/src/droid/Resources/layout/Tabbar.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/Directory.build.props:
--------------------------------------------------------------------------------
1 |
2 |
3 | 5.0.0.2401-pre1
4 | Alex Shirshov
5 | MVU library for Xamarin.Forms
6 | Write your Model-View-Update code for Xamarin.Forms in React+Redux fashion
7 | Copyright (c) Alex Shirshov. All rights reserved.
8 | MIT
9 | https://github.com/shirshov/laconic
10 |
11 |
12 |
--------------------------------------------------------------------------------
/maps/demo/ios/AppDelegate.cs:
--------------------------------------------------------------------------------
1 | using Foundation;
2 | using UIKit;
3 |
4 | namespace Laconic.Maps.Demo
5 | {
6 | [Register("AppDelegate")]
7 | public class AppDelegate : Xamarin.Forms.Platform.iOS.FormsApplicationDelegate
8 | {
9 | public override bool FinishedLaunching(UIApplication app, NSDictionary options)
10 | {
11 | Xamarin.Forms.Forms.Init();
12 | Xamarin.FormsMaps.Init();
13 |
14 | LoadApplication(new MapsApp());
15 |
16 | return base.FinishedLaunching(app, options);
17 | }
18 | }
19 | }
--------------------------------------------------------------------------------
/test/PickerTests.cs:
--------------------------------------------------------------------------------
1 | namespace Laconic.Tests;
2 |
3 | public class PickerTests
4 | {
5 | [Fact]
6 | public void do_not_update_Items_until_changed()
7 | {
8 | var binder = Binder.Create(0, (s, g) => 1);
9 | var picker = binder.CreateElement(s => new Picker
10 | {
11 | Items = new [] {"0", "1", "2"},
12 | SelectedIndex = s,
13 | SelectedIndexChanged = e => new Signal(e)
14 | });
15 |
16 | picker.SelectedIndex = 1;
17 |
18 | picker.SelectedIndex.ShouldBe(1);
19 | }
20 | }
--------------------------------------------------------------------------------
/cli-template/src/droid/Properties/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/demo/droid/Resources/layout/Tabbar.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/maps/demo/droid/Resources/layout/Tabbar.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/src/View.cs:
--------------------------------------------------------------------------------
1 | namespace Laconic;
2 |
3 | public interface View
4 | {
5 | Dictionary GestureRecognizers { get; }
6 | }
7 | public abstract class View : VisualElement, View where T : xf.View, new()
8 | {
9 | public LayoutOptions HorizontalOptions {
10 | init => SetValue(xf.View.HorizontalOptionsProperty, value);
11 | }
12 |
13 | public LayoutOptions VerticalOptions {
14 | init => SetValue(xf.View.VerticalOptionsProperty, value);
15 | }
16 |
17 | public Thickness Margin {
18 | init => SetValue(xf.View.MarginProperty, value);
19 | }
20 | }
--------------------------------------------------------------------------------
/codegen/src/metapackage/metapackage.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | netstandard2.0
7 | Laconic.CodeGeneration
8 | Laconic.CodeGeneration
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/cli-template/src/ios/AppDelegate.cs:
--------------------------------------------------------------------------------
1 | using Foundation;
2 | using UIKit;
3 |
4 | namespace Laconic.Template.iOS
5 | {
6 | [Register("AppDelegate")]
7 | public class AppDelegate : Xamarin.Forms.Platform.iOS.FormsApplicationDelegate
8 | {
9 | public override bool FinishedLaunching(UIApplication app, NSDictionary options)
10 | {
11 | Xamarin.Forms.Forms.Init();
12 | LoadApplication(new App());
13 |
14 | return base.FinishedLaunching(app, options);
15 | }
16 |
17 | static void Main(string[] args) => UIApplication.Main(args, null, "AppDelegate");
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/EventInfo.cs:
--------------------------------------------------------------------------------
1 | namespace Laconic;
2 |
3 | public class EventInfo
4 | {
5 | public readonly Func SignalMaker;
6 | public readonly Action Subscribe;
7 | public readonly Action Unsubscribe;
8 |
9 | public EventInfo(Func signalMaker,
10 | Action subscribe,
11 | Action unsubscribe)
12 | {
13 | SignalMaker = signalMaker;
14 | Subscribe = subscribe;
15 | Unsubscribe = unsubscribe;
16 | }
17 | }
--------------------------------------------------------------------------------
/maps/src/MapElement.cs:
--------------------------------------------------------------------------------
1 | namespace Laconic.Maps
2 | {
3 | public abstract class MapElement : Element where T : Xamarin.Forms.BindableObject
4 | {
5 | public Color StrokeColor
6 | {
7 | get => GetValue(Xamarin.Forms.Maps.MapElement.StrokeColorProperty);
8 | set => SetValue(Xamarin.Forms.Maps.MapElement.StrokeColorProperty, value);
9 | }
10 |
11 | public float StrokeWidth
12 | {
13 | get => GetValue(Xamarin.Forms.Maps.MapElement.StrokeWidthProperty);
14 | set => SetValue(Xamarin.Forms.Maps.MapElement.StrokeWidthProperty, value);
15 | }
16 | }
17 | }
--------------------------------------------------------------------------------
/Laconic.sln.DotSettings:
--------------------------------------------------------------------------------
1 |
6 | /Applications/Visual Studio.app/Contents/Resources/lib/monodevelop/bin/MSBuild/Current/bin/MSBuild.dll
7 | 4294967294
8 |
--------------------------------------------------------------------------------
/codegen/Directory.Build.props:
--------------------------------------------------------------------------------
1 |
2 |
3 | 0.9.3
4 | 0.9.3-beta
5 | Alex Shirshov
6 | Records and Unions for C# 8
7 | Compile-time code generation of records and discriminated unions for C# 8
8 | Copyright (c) Alex Shirshov. All rights reserved.
9 | MIT
10 | https://github.com/shirshov/laconic/tree/master/codegen
11 |
12 |
--------------------------------------------------------------------------------
/maps/src/Laconic.Maps.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | netstandard2.1
5 | 9
6 | enable
7 | Binding for Xamarin.Forms.Maps for use with Laconic
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/src/GestureRecognizers.cs:
--------------------------------------------------------------------------------
1 | namespace Laconic;
2 |
3 | public interface IGestureRecognizer
4 | {
5 | }
6 |
7 | public class TapGestureRecognizer : Element, IGestureRecognizer
8 | {
9 | public int NumberOfTapsRequired
10 | {
11 | set => SetValue(xf.TapGestureRecognizer.NumberOfTapsRequiredProperty, value);
12 | }
13 |
14 | public System.Func Tapped
15 | {
16 | set => SetEvent(nameof(Tapped), value,
17 | (ctl, handler) => ctl.Tapped += handler,
18 | (ctl, handler) => ctl.Tapped -= handler);
19 | }
20 |
21 | protected internal override xf.BindableObject CreateView() => new xf.TapGestureRecognizer();
22 | }
--------------------------------------------------------------------------------
/cli-template/src/Laconic.Template.sln.DotSettings:
--------------------------------------------------------------------------------
1 |
6 | /Applications/Visual Studio.app/Contents/Resources/lib/monodevelop/bin/MSBuild/Current/bin/MSBuild.dll
7 | 4294967294
8 |
--------------------------------------------------------------------------------
/src/Signal.cs:
--------------------------------------------------------------------------------
1 | namespace Laconic;
2 |
3 | public class Signal
4 | {
5 | public readonly object? Payload;
6 | readonly object? _p1;
7 | readonly object? _p2;
8 |
9 | public Signal(object? payload) => (Payload, _p1, _p2) = (payload, payload, null);
10 | public Signal(object p1, object? p2) => (Payload, _p1, _p2) = (p1, p1, p2);
11 |
12 | public void Deconstruct(out object? p1, out object? p2) => (p1, p2) = (_p1, _p2);
13 |
14 | public override string ToString() => $"{GetType()}: {_p1} {_p2}";
15 | }
16 |
17 | public class Signal : Signal
18 | {
19 | public Signal(T payload) : base(payload)
20 | {
21 | }
22 |
23 | public new T Payload => (T) base.Payload!;
24 | }
--------------------------------------------------------------------------------
/src/AbsoluteLayoutDiff.cs:
--------------------------------------------------------------------------------
1 | namespace Laconic;
2 |
3 | static class AbsoluteLayoutDiff
4 | {
5 | public static IEnumerable Calculate(
6 | Key key, AbsoluteLayoutViewList? existingList, AbsoluteLayoutViewList newList)
7 | {
8 | var existingPos = new AbsLayoutInfo(new Bounds(-1, -1, -1, -1), AbsoluteLayoutFlags.None);
9 | if (existingList != null && existingList.ContainsKey(key))
10 | existingPos = existingList.GetPositioning(key);
11 |
12 | var newPos = newList.GetPositioning(key);
13 |
14 | if (newPos != existingPos)
15 | yield return new SetAbsoluteLayoutPositioning(newPos.Bounds, newPos.Flags);
16 | }
17 | }
--------------------------------------------------------------------------------
/cli-template/src/app/Laconic.Template.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | netstandard2.1
5 | true
6 |
7 |
8 |
9 | portable
10 | true
11 | 9.0
12 |
13 |
14 |
15 | 9.0
16 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/demo/app/AbsoluteLayoutPage.cs:
--------------------------------------------------------------------------------
1 | namespace Laconic.Demo;
2 |
3 | static class AbsoluteLayoutPage
4 | {
5 | public static AbsoluteLayout Content() => new() {
6 | ["b", (0.5, 0, 100, 25), AbsoluteLayoutFlags.PositionProportional] = new BoxView { Color = Color.Blue},
7 | ["g", (0 ,0.5, 25, 100), AbsoluteLayoutFlags.PositionProportional] = new BoxView { Color = Color.Green},
8 | ["r", (1, 0.5,25,100), AbsoluteLayoutFlags.PositionProportional] = new BoxView { Color = Color.Red},
9 | ["k", (0.5,1,100,25), AbsoluteLayoutFlags.PositionProportional] = new BoxView { Color = Color.Black},
10 | ["t", (0.5, 0.5, 110, 25), AbsoluteLayoutFlags.PositionProportional] = new Label {
11 | Text = "Centered text",
12 | }
13 | };
14 | }
--------------------------------------------------------------------------------
/maps/demo/app/Maps.Demo.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | netstandard2.1
5 | 9
6 |
7 |
8 |
9 | portable
10 | true
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/codegen/test/CodeGen.Tests.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 | netcoreapp2.1
4 | 8.0
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/cli-template/Package.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Template
5 | 1.0.3
6 | Laconic.AppTemplate
7 | Laconic App Template
8 | Alex Shirshov
9 | Template for creating Xamarin.Forms + Laconic apps
10 | laconic;xamarin.forms
11 |
12 | netcoreapp3.1
13 |
14 | false
15 | $(NoWarn);NU5118;NU5119;NU5128
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/codegen/src/attributes/Attributes.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using CodeGeneration.Roslyn;
3 |
4 | namespace Laconic.CodeGeneration
5 | {
6 | [AttributeUsage(AttributeTargets.Interface)]
7 | [CodeGenerationAttribute("Laconic.CodeGeneration.UnionGenerator, Laconic.CodeGen.Generators")]
8 | public class UnionAttribute : Attribute
9 | {
10 | }
11 |
12 | [AttributeUsage(AttributeTargets.Interface)]
13 | [CodeGenerationAttribute("Laconic.CodeGeneration.RecordsGenerator, Laconic.CodeGen.Generators")]
14 | public class RecordsAttribute : Attribute
15 | {
16 | }
17 |
18 | [AttributeUsage(AttributeTargets.Interface)]
19 | [CodeGenerationAttribute("Laconic.CodeGeneration.SignalsGenerator, Laconic.CodeGen.Generators")]
20 | public class SignalsAttribute : Attribute
21 | {
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/demo/droid/MainActivity.cs:
--------------------------------------------------------------------------------
1 | using Android.App;
2 | using Android.Content.PM;
3 | using Android.OS;
4 |
5 | namespace Laconic.Demo
6 | {
7 | [Activity(Label = "Laconic Demo", Icon = "@mipmap/icon", Theme = "@style/MainTheme", MainLauncher = true,
8 | ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation)]
9 | public class MainActivity : global::Xamarin.Forms.Platform.Android.FormsAppCompatActivity
10 | {
11 | protected override void OnCreate(Bundle savedInstanceState)
12 | {
13 | TabLayoutResource = Resource.Layout.Tabbar;
14 | ToolbarResource = Resource.Layout.Toolbar;
15 |
16 | base.OnCreate(savedInstanceState);
17 |
18 | Xamarin.Forms.Forms.Init(this, savedInstanceState);
19 | LoadApplication(new App());
20 | }
21 | }
22 | }
--------------------------------------------------------------------------------
/test/LaconicTests.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net6.0
5 | 10
6 | enable
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 | all
15 | runtime; build; native; contentfiles; analyzers; buildtransitive
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/maps/demo/droid/MainActivity.cs:
--------------------------------------------------------------------------------
1 | using Android.App;
2 | using Android.Content.PM;
3 | using Android.OS;
4 |
5 | namespace Laconic.Maps.Demo
6 | {
7 | [Activity(Label = "Laconic.Maps Demo", Icon = "@mipmap/icon", Theme = "@style/MainTheme", MainLauncher = true,
8 | ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation)]
9 | public class MainActivity : global::Xamarin.Forms.Platform.Android.FormsAppCompatActivity
10 | {
11 | protected override void OnCreate(Bundle savedInstanceState)
12 | {
13 | TabLayoutResource = Resource.Layout.Tabbar;
14 | ToolbarResource = Resource.Layout.Toolbar;
15 |
16 | base.OnCreate(savedInstanceState);
17 |
18 | Xamarin.Forms.Forms.Init(this, savedInstanceState);
19 | LoadApplication(new App());
20 | }
21 | }
22 | }
--------------------------------------------------------------------------------
/maps/src/Polygon.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 |
3 | namespace Laconic.Maps
4 | {
5 | public class Polygon : MapElement
6 | {
7 | protected override Xamarin.Forms.BindableObject CreateView() => new Xamarin.Forms.Maps.Polygon();
8 |
9 | public Color FillColor
10 | {
11 | get => GetValue(Xamarin.Forms.Maps.Polygon.FillColorProperty);
12 | set => SetValue(Xamarin.Forms.Maps.Polygon.FillColorProperty, value);
13 | }
14 |
15 | public List Geopath {
16 | set => SetValue(nameof(Geopath), value, polygon => {
17 | polygon.Geopath.Clear();
18 | foreach (var x in value)
19 | polygon.Geopath.Add(new Xamarin.Forms.Maps.Position(x.Latitude, x.Longitude));
20 | });
21 | }
22 | }
23 | }
--------------------------------------------------------------------------------
/demo/app/Demo.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | netstandard2.1
5 | true
6 | 10
7 | enable
8 |
9 |
10 |
11 | portable
12 | true
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/maps/src/Pin.cs:
--------------------------------------------------------------------------------
1 | namespace Laconic.Maps
2 | {
3 | public class Pin : Element
4 | {
5 | public PinType Type
6 | {
7 | set => SetValue(Xamarin.Forms.Maps.Pin.TypeProperty, (Xamarin.Forms.Maps.PinType)value);
8 | }
9 |
10 | public Position Position
11 | {
12 | set => SetValue(Xamarin.Forms.Maps.Pin.PositionProperty, new Xamarin.Forms.Maps.Position(value.Latitude, value.Longitude));
13 | }
14 |
15 | public string Label
16 | {
17 | set => SetValue(Xamarin.Forms.Maps.Pin.LabelProperty, value);
18 | }
19 |
20 | public string Address
21 | {
22 | set => SetValue(Xamarin.Forms.Maps.Pin.AddressProperty, value);
23 | }
24 |
25 | protected override Xamarin.Forms.BindableObject CreateView() => new Xamarin.Forms.Maps.Pin();
26 | }
27 | }
--------------------------------------------------------------------------------
/cli-template/src/droid/MainActivity.cs:
--------------------------------------------------------------------------------
1 | using Android.App;
2 | using Android.Content.PM;
3 | using Android.OS;
4 |
5 | namespace Laconic.Template.Droid
6 | {
7 | [Activity(Label = "Laconic.Template", Icon = "@mipmap/icon", Theme = "@style/MainTheme", MainLauncher = true, ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation | ConfigChanges.UiMode | ConfigChanges.ScreenLayout | ConfigChanges.SmallestScreenSize)]
8 | public class MainActivity : Xamarin.Forms.Platform.Android.FormsAppCompatActivity
9 | {
10 | protected override void OnCreate(Bundle savedInstanceState)
11 | {
12 | TabLayoutResource = Resource.Layout.Tabbar;
13 | ToolbarResource = Resource.Layout.Toolbar;
14 |
15 | base.OnCreate(savedInstanceState);
16 |
17 | Xamarin.Forms.Forms.Init(this, savedInstanceState);
18 | LoadApplication(new App());
19 | }
20 | }
21 | }
--------------------------------------------------------------------------------
/demo/app/Counter.cs:
--------------------------------------------------------------------------------
1 | namespace Laconic.Demo;
2 |
3 | static class Counter
4 | {
5 | public static StackLayout Content(int state) => new() {
6 | Padding = 50,
7 | ["lbl"] = new Label
8 | {
9 | Text = $"You clicked {state} times",
10 | FontSize = 30,
11 | FontAttributes = FontAttributes.Bold,
12 | VerticalOptions = LayoutOptions.CenterAndExpand,
13 | HorizontalOptions = LayoutOptions.Center
14 | },
15 | ["btn"] = new Button
16 | {
17 | Text = "Click Me",
18 | Clicked = () => new Signal("inc"),
19 | TextColor = Color.White,
20 | FontSize = 20,
21 | BackgroundColor = Color.Coral,
22 | BorderColor = Color.Chocolate,
23 | BorderWidth = 2,
24 | CornerRadius = 20,
25 | HorizontalOptions = LayoutOptions.Center,
26 | Padding = (30, 0)
27 | }
28 | };
29 | }
--------------------------------------------------------------------------------
/demo/app/FormattedStringPage.cs:
--------------------------------------------------------------------------------
1 | namespace Laconic.Demo;
2 |
3 | static class FormattedStringPage
4 | {
5 | public static Label Content() => new() {
6 | Margin = (30, 30),
7 | VerticalOptions = LayoutOptions.Center,
8 | FormattedText = new FormattedString {
9 | new Span {Text = "As ", FontFamily = "AmericanTypewriter" }, // the font is iOS only
10 | new Span {Text = "you ", FontSize = 30},
11 | new Span {Text = "value ", FontAttributes = FontAttributes.Bold | FontAttributes.Italic},
12 | new Span {Text = "your life or ",BackgroundColor = Color.Yellow },
13 | new Span {Text = "your ", CharacterSpacing = 10},
14 | new Span {Text = "reason ", LineHeight = 15},
15 | new Span {Text = "keep away ", TextColor = Color.Blue},
16 | new Span {Text = "from the ", TextDecorations = TextDecorations.Underline, },
17 | "moor" // plain text is allowed
18 | }
19 | };
20 | }
--------------------------------------------------------------------------------
/test/StackLayoutTests.cs:
--------------------------------------------------------------------------------
1 | namespace Laconic.Tests;
2 |
3 | public class StackLayoutTests
4 | {
5 | [Fact]
6 | public void can_add_children()
7 | {
8 | var sl = new StackLayout {
9 | [1] = new Label { Text = "lbl1"},
10 | [2] = new Label { Text = "lbl2" }
11 | };
12 |
13 | sl.Children.Count().ShouldBe(2);
14 |
15 | var binder = Binder.Create("state", (state, _) => state);
16 | var real = binder.CreateElement(state => new StackLayout {
17 | ["1"] = new Label { Text = "lbl1"},
18 | ["2"] = new Label { Text = "lbl2" }
19 | });
20 |
21 | real.Children.Count.ShouldBe(2);
22 | real.Children[0].ShouldBeOfType();
23 | real.Children[1].ShouldBeOfType();
24 | }
25 |
26 | [Fact]
27 | public void children_from_LINQ()
28 | {
29 | var s = new StackLayout {
30 | Children = Enumerable.Range(1, 5).ToViewList(i => i, i => new Label {Text = "Item " + i})
31 | };
32 | }
33 | }
--------------------------------------------------------------------------------
/src/ToViewListExtensions.cs:
--------------------------------------------------------------------------------
1 | namespace Laconic;
2 |
3 | public static class ToViewListExtensions
4 | {
5 | public static Dictionary ToViewList(this IEnumerable source,
6 | Func keySelector, Func itemSelector) =>
7 | source.ToDictionary(keySelector, itemSelector);
8 |
9 | public static Dictionary<(Key, int Row, int Column), View> ToGridViewList(this IEnumerable source,
10 | Func keySelector, Func itemSelector) =>
11 | source.ToDictionary(keySelector, itemSelector);
12 |
13 | public static ItemsViewList ToItemsList(this IEnumerable source,
14 | Func reuseKeySelector, Func keySelector, Func viewSelector)
15 | {
16 | var res = new ItemsViewList();
17 | foreach (var item in source)
18 | {
19 | var key = keySelector(item);
20 | res.Add(key, viewSelector(item));
21 | res.ReuseKeys[key] = reuseKeySelector(item);
22 | }
23 |
24 | return res;
25 | }
26 | }
--------------------------------------------------------------------------------
/cli-template/src/.template.config/template.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "http://json.schemastore.org/template",
3 | "author": "Alex Shirshov",
4 | "name": "Laconic Mobile App",
5 | "identity": "Laconic.AppTemplate",
6 | "description": "Xamarin-based mobile app template using React + Redux like approach",
7 | "shortName": "laconicapp",
8 | "classifications": [
9 | "Xamarin.Forms",
10 | "Laconic"
11 | ],
12 | "tags": {
13 | "language": "C#",
14 | "type": "project"
15 | },
16 | "sourceName": "Laconic.Template",
17 | "preferNameDirectory": true,
18 | "guids": [
19 | "{A4EE6D41-06D6-41B6-85C5-C53BFFBB4896}",
20 | "{BD92F4FD-91AE-48EB-8721-6FCAA849717B}",
21 | "{ECF3B85F-6039-4671-B95E-EAB7E3A70EB1}"
22 | ],
23 | "symbols": {
24 | "organizationIdentifier": {
25 | "type": "parameter",
26 | "description": "Organization Identifier for bundle ID",
27 | "defaultValue": "com.companyname",
28 | "replaces": "com.companyname"
29 | }
30 | }
31 | }
--------------------------------------------------------------------------------
/src/VisualElement.cs:
--------------------------------------------------------------------------------
1 | namespace Laconic;
2 |
3 | public abstract partial class VisualElement : Element where T : xf.VisualElement, new()
4 | {
5 | // TODO: why is it here, and not on Element?
6 | protected internal override xf.BindableObject CreateView() => new T();
7 |
8 | public Dictionary GestureRecognizers { get; } = new();
9 |
10 | public VisualElement() => ElementLists.Add(nameof(Behaviors), element => (IList)element.Behaviors);
11 |
12 | public VisualMarker Visual
13 | {
14 | get => GetValue(xf.VisualElement.VisualProperty);
15 | set => SetValue(xf.VisualElement.VisualProperty, value);
16 | }
17 |
18 | public IBrush Background
19 | {
20 | get => GetValue(xf.VisualElement.BackgroundProperty);
21 | set => SetValue(xf.VisualElement.BackgroundProperty, value);
22 | }
23 |
24 | public ElementList Behaviors {
25 | get => ElementLists[nameof(Behaviors)];
26 | set => ElementLists[nameof(Behaviors)] = value;
27 | }
28 | }
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 Alex Shirshov
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/demo/app/Shapes.cs:
--------------------------------------------------------------------------------
1 | using Laconic.Shapes;
2 |
3 | namespace Laconic.Demo;
4 |
5 | static class Shapes
6 | {
7 | public static ScrollView Content() => new() {
8 | Padding = 20,
9 | Content = new StackLayout {
10 | ["line"] = new Line {
11 | X1 = 40,
12 | Y1 = 0,
13 | X2 = 0,
14 | Y2 = 120,
15 | Stroke = Brush.Red,
16 | StrokeThickness = 5
17 | },
18 | ["rect"] = new Rectangle {
19 | Fill = Brush.Blue,
20 | Stroke = Brush.Black,
21 | StrokeThickness = 3,
22 | RadiusX = 50,
23 | RadiusY = 10,
24 | WidthRequest = 200,
25 | HeightRequest = 100,
26 | HorizontalOptions = LayoutOptions.Start
27 | },
28 | ["path"] = new Path {
29 | Data = "M 10,100 C 100,0 200,200 300,100",
30 | Stroke = Brush.Black,
31 | StrokeThickness = 3,
32 | Aspect = Stretch.Uniform
33 | }
34 | }
35 | };
36 | }
--------------------------------------------------------------------------------
/maps/test/MapTests.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net5.0
5 | 8.0
6 | enable
7 |
8 |
9 |
10 | 9.0
11 |
12 |
13 | 9.0
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 | all
25 | runtime; build; native; contentfiles; analyzers; buildtransitive
26 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/demo/ios/AppDelegate.cs:
--------------------------------------------------------------------------------
1 | using Foundation;
2 | using UIKit;
3 |
4 | namespace Laconic.Demo
5 | {
6 | // The UIApplicationDelegate for the application. This class is responsible for launching the
7 | // User Interface of the application, as well as listening (and optionally responding) to
8 | // application events from iOS.
9 | [Register("AppDelegate")]
10 | public class AppDelegate : Xamarin.Forms.Platform.iOS.FormsApplicationDelegate
11 | {
12 | //
13 | // This method is invoked when the application has loaded and is ready to run. In this
14 | // method you should instantiate the window, load the UI into it and then make the window
15 | // visible.
16 | //
17 | // You have 17 seconds to return from this method, or iOS will terminate your application.
18 | //
19 | public override bool FinishedLaunching(UIApplication app, NSDictionary options)
20 | {
21 | Xamarin.Forms.Forms.SetFlags("RadioButton_Experimental");
22 | Xamarin.Forms.Forms.Init();
23 | LoadApplication(new App());
24 |
25 | return base.FinishedLaunching(app, options);
26 | }
27 | }
28 | }
--------------------------------------------------------------------------------
/src/FormattedString.cs:
--------------------------------------------------------------------------------
1 | namespace Laconic;
2 |
3 | public partial class Span : Element
4 | {
5 | protected internal override xf.BindableObject CreateView() => throw new NotImplementedException();
6 | }
7 |
8 | public class FormattedString : IConvert, IEnumerable
9 | {
10 | readonly List _spans = new();
11 |
12 | public void Add(string text) => _spans.Add(new Span{Text = text});
13 |
14 | public void Add(Span span) => _spans.Add(span);
15 |
16 | public IEnumerator GetEnumerator() => throw new NotImplementedException();
17 |
18 | IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
19 |
20 | object IConvert.ToNative()
21 | {
22 | // TODO: this is very inefficient. Must plug FormattedString into diff/patch properly.
23 | var ret = new xf.FormattedString();
24 | foreach (var span in _spans) {
25 | var newSpan = new xf.Span();
26 | foreach (var p in span.ProvidedValues)
27 | if (p.Value != null)
28 | newSpan.SetValue(p.Key, Patch.ConvertToNative(p.Value));
29 | ret.Spans.Add(newSpan);
30 | }
31 |
32 | return ret;
33 | }
34 | }
--------------------------------------------------------------------------------
/demo/droid/Properties/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Reflection;
2 | using System.Runtime.CompilerServices;
3 | using System.Runtime.InteropServices;
4 | using Android.App;
5 |
6 | // General Information about an assembly is controlled through the following
7 | // set of attributes. Change these attribute values to modify the information
8 | // associated with an assembly.
9 | [assembly: AssemblyTitle("Counter2.Android")]
10 | [assembly: AssemblyDescription("")]
11 | [assembly: AssemblyConfiguration("")]
12 | [assembly: AssemblyCompany("")]
13 | [assembly: AssemblyProduct("Counter2.Android")]
14 | [assembly: AssemblyCopyright("Copyright © 2014")]
15 | [assembly: AssemblyTrademark("")]
16 | [assembly: AssemblyCulture("")]
17 | [assembly: ComVisible(false)]
18 |
19 | // Version information for an assembly consists of the following four values:
20 | //
21 | // Major Version
22 | // Minor Version
23 | // Build Number
24 | // Revision
25 | [assembly: AssemblyVersion("1.0.0.0")]
26 | [assembly: AssemblyFileVersion("1.0.0.0")]
27 |
28 | // Add some common permissions, these can be removed if not needed
29 | [assembly: UsesPermission(Android.Manifest.Permission.Internet)]
30 | [assembly: UsesPermission(Android.Manifest.Permission.WriteExternalStorage)]
31 |
--------------------------------------------------------------------------------
/test/ImageSourceTests.cs:
--------------------------------------------------------------------------------
1 | namespace Laconic.Tests;
2 |
3 | public class ImageSourceTests
4 | {
5 | [Fact]
6 | public void FontImageSource_can_be_created_and_updated()
7 | {
8 | var bp1 = new Image {
9 | Source = new FontImageSource {FontFamily = "Arial", Glyph = "a", Size = 13, Color = Color.Red}
10 | };
11 | var bp2 = new Image {
12 | Source = new FontImageSource {FontFamily = "Helvetica", Glyph = "h", Size = 15, Color = Color.Green}
13 | };
14 |
15 | var binder = Binder.CreateForTest(0, (s, g) => s + 1);
16 | var img = binder.CreateElement(s => s == 0 ? bp1 : bp2);
17 |
18 | var imgSource = img.Source.ShouldBeOfType();
19 |
20 | imgSource.FontFamily.ShouldBe("Arial");
21 | imgSource.Glyph.ShouldBe("a");
22 | imgSource.Size.ShouldBe(13);
23 | imgSource.Color.ShouldBe(xf.Color.Red);
24 |
25 | binder.Send(new Signal(null));
26 |
27 | imgSource = img.Source.ShouldBeOfType();
28 |
29 | imgSource.FontFamily.ShouldBe("Helvetica");
30 | imgSource.Glyph.ShouldBe("h");
31 | imgSource.Size.ShouldBe(15);
32 | imgSource.Color.ShouldBe(xf.Color.Green);
33 | }
34 | }
--------------------------------------------------------------------------------
/demo/app/EntryAndEditor.cs:
--------------------------------------------------------------------------------
1 | namespace Laconic.Demo;
2 |
3 | static class EntryAndEditor
4 | {
5 | public static VisualElement Content() => Element.WithContext("entry", ctx => {
6 | var (text, setState) = ctx.UseLocalState("");
7 | return new StackLayout {
8 | BackgroundColor = Color.Bisque,
9 | Padding = 20,
10 | Spacing = 20,
11 | ["entry label"] = new Label {Text = "Entry:"},
12 | ["entry"] =
13 | new Entry {
14 | Text = text, Placeholder = "Type something", TextChanged = e => setState(e.NewTextValue)
15 | },
16 | ["editor label"] = new Label {Text = "Editor:"},
17 | ["editor"] =
18 | new Editor {
19 | Placeholder = "Type something",
20 | HeightRequest = 100,
21 | Text = text,
22 | TextChanged = e => setState(e.NewTextValue)
23 | },
24 | ["Numbers label"] = new Label {Text = "Entry with numeric keyboard:"},
25 | ["numbers entry"] = new Entry {
26 | Placeholder = "Type something",
27 | Keyboard = Keyboard.Numeric,
28 | Text = text,
29 | TextChanged = e => setState(e.NewTextValue)
30 | }
31 | };
32 | });
33 | }
--------------------------------------------------------------------------------
/demo/app/Timer.cs:
--------------------------------------------------------------------------------
1 | namespace Laconic.Demo;
2 |
3 | static class Timer
4 | {
5 | static StackLayout View(TimeSpan elapsed, string buttonTitle, Func buttonAction) => new() {
6 | Spacing = 30,
7 | Padding = 30,
8 | ["display"] = new Label {
9 | Text = elapsed.TotalSeconds.ToString("0.0"),
10 | FontFamily = "DINBold",
11 | FontSize = 50,
12 | FontAttributes = FontAttributes.Bold,
13 | HorizontalOptions = LayoutOptions.Center
14 | },
15 | ["button"] = new Button {
16 | Text = buttonTitle,
17 | TextColor = Color.White,
18 | FontSize = 20,
19 | Clicked = buttonAction,
20 | BackgroundColor = Color.Coral,
21 | BorderColor = Color.Chocolate,
22 | BorderWidth = 2,
23 | CornerRadius = 20,
24 | HorizontalOptions = LayoutOptions.Center,
25 | Padding = (30, 0)
26 | }
27 | };
28 |
29 | public static VisualElement Content() => Element.WithContext("timer", ctx => {
30 | var timer = ctx.UseTimer(TimeSpan.FromMilliseconds(100), start: false);
31 |
32 | return View(timer.Elapsed,
33 | timer.IsRunning ? "Stop" : "Start",
34 | timer.IsRunning ? () => timer.Stop() : () => timer.Start()
35 | );
36 | });
37 | }
--------------------------------------------------------------------------------
/maps/src/Position.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace Laconic.Maps
4 | {
5 | public readonly struct Position
6 | {
7 | public Position(double latitude, double longitude)
8 | {
9 | Latitude = Math.Min(Math.Max(latitude, -90.0), 90.0);
10 | Longitude = Math.Min(Math.Max(longitude, -180.0), 180.0);
11 | }
12 |
13 | public double Latitude { get; }
14 | public double Longitude { get; }
15 |
16 | public override bool Equals(object obj)
17 | {
18 | if (obj == null || obj.GetType() != this.GetType())
19 | return false;
20 | var position = (Position) obj;
21 | return this.Latitude == position.Latitude && this.Longitude == position.Longitude;
22 | }
23 |
24 | public override int GetHashCode()
25 | {
26 | var num1 = Latitude;
27 | var num2 = num1.GetHashCode() * 397;
28 | num1 = Longitude;
29 | var hashCode = num1.GetHashCode();
30 | return num2 ^ hashCode;
31 | }
32 |
33 | public static bool operator ==(Position left, Position right) => Equals(left, right);
34 |
35 | public static bool operator !=(Position left, Position right) => !Equals(left, right);
36 |
37 | public static implicit operator Position((double latitude, double longitude) value) =>
38 | new Position(value.latitude, value.longitude);
39 | }
40 | }
--------------------------------------------------------------------------------
/demo/ios/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | UIDeviceFamily
6 |
7 | 1
8 | 2
9 |
10 | UISupportedInterfaceOrientations
11 |
12 | UIInterfaceOrientationPortrait
13 | UIInterfaceOrientationLandscapeLeft
14 | UIInterfaceOrientationLandscapeRight
15 |
16 | UISupportedInterfaceOrientations~ipad
17 |
18 | UIInterfaceOrientationPortrait
19 | UIInterfaceOrientationPortraitUpsideDown
20 | UIInterfaceOrientationLandscapeLeft
21 | UIInterfaceOrientationLandscapeRight
22 |
23 | MinimumOSVersion
24 | 8.0
25 | CFBundleDisplayName
26 | Laconic Demo
27 | CFBundleIdentifier
28 | laconic.demo
29 | CFBundleVersion
30 | 1.0
31 | UILaunchStoryboardName
32 | LaunchScreen
33 | CFBundleName
34 | Laconic.Demo
35 | XSAppIconAssets
36 | Assets.xcassets/AppIcon.appiconset
37 |
38 |
39 |
--------------------------------------------------------------------------------
/demo/ios/Properties/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Reflection;
2 | using System.Runtime.InteropServices;
3 |
4 | // General Information about an assembly is controlled through the following
5 | // set of attributes. Change these attribute values to modify the information
6 | // associated with an assembly.
7 | [assembly: AssemblyTitle("Counter2.iOS")]
8 | [assembly: AssemblyDescription("")]
9 | [assembly: AssemblyConfiguration("")]
10 | [assembly: AssemblyCompany("")]
11 | [assembly: AssemblyProduct("Counter2.iOS")]
12 | [assembly: AssemblyCopyright("Copyright © 2014")]
13 | [assembly: AssemblyTrademark("")]
14 | [assembly: AssemblyCulture("")]
15 |
16 | // Setting ComVisible to false makes the types in this assembly not visible
17 | // to COM components. If you need to access a type in this assembly from
18 | // COM, set the ComVisible attribute to true on that type.
19 | [assembly: ComVisible(false)]
20 |
21 | // The following GUID is for the ID of the typelib if this project is exposed to COM
22 | [assembly: Guid("72bdc44f-c588-44f3-b6df-9aace7daafdd")]
23 |
24 | // Version information for an assembly consists of the following four values:
25 | //
26 | // Major Version
27 | // Minor Version
28 | // Build Number
29 | // Revision
30 | //
31 | // You can specify all the values or you can default the Build and Revision Numbers
32 | // by using the '*' as shown below:
33 | // [assembly: AssemblyVersion("1.0.*")]
34 | [assembly: AssemblyVersion("1.0.0.0")]
35 | [assembly: AssemblyFileVersion("1.0.0.0")]
--------------------------------------------------------------------------------
/maps/demo/ios/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | UIDeviceFamily
6 |
7 | 1
8 | 2
9 |
10 | UISupportedInterfaceOrientations
11 |
12 | UIInterfaceOrientationPortrait
13 | UIInterfaceOrientationLandscapeLeft
14 | UIInterfaceOrientationLandscapeRight
15 |
16 | UISupportedInterfaceOrientations~ipad
17 |
18 | UIInterfaceOrientationPortrait
19 | UIInterfaceOrientationPortraitUpsideDown
20 | UIInterfaceOrientationLandscapeLeft
21 | UIInterfaceOrientationLandscapeRight
22 |
23 | MinimumOSVersion
24 | 8.0
25 | CFBundleDisplayName
26 | Laconic.Maps Demo
27 | CFBundleIdentifier
28 | laconic.maps.demo
29 | CFBundleVersion
30 | 1.0
31 | UILaunchStoryboardName
32 | LaunchScreen
33 | CFBundleName
34 | Laconic.Maps.Demo
35 | XSAppIconAssets
36 | Assets.xcassets/AppIcon.appiconset
37 |
38 |
39 |
--------------------------------------------------------------------------------
/cli-template/src/ios/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | UIDeviceFamily
6 |
7 | 1
8 | 2
9 |
10 | UISupportedInterfaceOrientations
11 |
12 | UIInterfaceOrientationPortrait
13 | UIInterfaceOrientationLandscapeLeft
14 | UIInterfaceOrientationLandscapeRight
15 |
16 | UISupportedInterfaceOrientations~ipad
17 |
18 | UIInterfaceOrientationPortrait
19 | UIInterfaceOrientationPortraitUpsideDown
20 | UIInterfaceOrientationLandscapeLeft
21 | UIInterfaceOrientationLandscapeRight
22 |
23 | MinimumOSVersion
24 | 8.0
25 | CFBundleDisplayName
26 | Laconic.Template
27 | CFBundleIdentifier
28 | com.companyname.Laconic.Template
29 | CFBundleVersion
30 | 1.0
31 | UILaunchStoryboardName
32 | LaunchScreen
33 | CFBundleName
34 | Laconic.Template
35 | XSAppIconAssets
36 | Assets.xcassets/AppIcon.appiconset
37 |
38 |
39 |
--------------------------------------------------------------------------------
/demo/app/Brushes.cs:
--------------------------------------------------------------------------------
1 | namespace Laconic.Demo;
2 |
3 | static class Brushes
4 | {
5 | public static StackLayout Content() => new() {
6 | Padding = 50,
7 | ["solid"] = new Frame {
8 | Background = Brush.DarkBlue,
9 | BorderColor = Color.LightGray,
10 | HasShadow = true,
11 | CornerRadius = 12,
12 | HeightRequest = 120,
13 | WidthRequest = 120
14 | },
15 | ["linear"] = new Frame {
16 | BorderColor = Color.LightGray,
17 | HasShadow = true,
18 | CornerRadius = 12,
19 | HeightRequest = 120,
20 | WidthRequest = 120,
21 | Background = new LinearGradientBrush {
22 | StartPoint = (0, 0),
23 | EndPoint = (1, 0),
24 | GradientStops = {
25 | [0] = new GradientStop(Color.Yellow, 0.1f),
26 | [1] = new GradientStop(Color.Green, 1.0f),
27 | }
28 | }
29 | },
30 | ["radial"] = new Frame {
31 | BorderColor = Color.LightGray,
32 | HasShadow = true,
33 | CornerRadius = 12,
34 | HeightRequest = 120,
35 | WidthRequest = 120,
36 | Background = new RadialGradientBrush {
37 | Center = (0.5, 0.5),
38 | Radius = (0.5),
39 | GradientStops = {
40 | [0] = new GradientStop(Color.Red, 0.1f),
41 | [1] = new GradientStop(Color.DarkBlue, 1.0f),
42 | }
43 | }
44 | }
45 | };
46 | }
--------------------------------------------------------------------------------
/src/TabbedPage.cs:
--------------------------------------------------------------------------------
1 | namespace Laconic;
2 |
3 | public class CurrentPageChangedEventArgs : EventArgs
4 | {
5 | public int Index { get; }
6 |
7 | public CurrentPageChangedEventArgs(int index) => Index = index;
8 | }
9 |
10 | public partial class TabbedPage : Page
11 | {
12 | public TabbedPage() => ElementLists.Add(nameof(Children), tp => (IList) tp.Children);
13 |
14 | public ElementList Children => ElementLists[nameof(Children)];
15 |
16 | public int CurrentPage {
17 | init => SetValue(nameof(CurrentPage), value, tp => {
18 | tp.CurrentPage = tp.Children[value];
19 | });
20 | }
21 |
22 | public Page this[Key key]
23 | {
24 | init => Children[key] = (Element)value;
25 | }
26 |
27 | EventHandler? _handler;
28 |
29 | void OnCurrentPageChanged(object sender, EventArgs args)
30 | {
31 | var page = (Xamarin.Forms.TabbedPage) sender;
32 | var index = page.Children.IndexOf(page.CurrentPage);
33 | if (index != -1)
34 | _handler!(page, new CurrentPageChangedEventArgs(index));
35 | }
36 |
37 | public Func CurrentPageChanged {
38 | init => SetEvent(nameof(CurrentPageChanged), value,
39 | (ctl, handler) => {
40 | _handler = handler;
41 | ctl.CurrentPageChanged += OnCurrentPageChanged;
42 | },
43 | (ctl, _) => {
44 | _handler = null;
45 | ctl.CurrentPageChanged -= OnCurrentPageChanged;
46 | });
47 | }
48 | }
--------------------------------------------------------------------------------
/test/ShapeTests.cs:
--------------------------------------------------------------------------------
1 | using Laconic.Shapes;
2 |
3 | namespace Laconic.Tests;
4 |
5 | public class ShapeTests
6 | {
7 | [Fact]
8 | public void Line_diff()
9 | {
10 | var noDiff = Diff.Calculate(new Line {X1 = 1, Y1 = 2, X2 = 3, Y2 = 4},
11 | new Line {X1 = 1, Y1 = 2, X2 = 3, Y2 = 4});
12 | noDiff.Count().ShouldBe(0);
13 |
14 | var diff = Diff.Calculate(new Line {X1 = 100, Y1 = 2, X2 = 3, Y2 = 4},
15 | new Line {X1 = 1, Y1 = 2, X2 = 3, Y2 = 4});
16 | var prop = diff.First().ShouldBeOfType();
17 | prop.Property.ShouldBe(xf.Shapes.Line.X1Property);
18 | prop.Value.ShouldBe(1);
19 | }
20 |
21 | [Fact]
22 | public void Clip_property_is_set()
23 | {
24 | var img = new Image {Clip = new EllipseGeometry {Center = new xf.Point(10, 10), RadiusX = 3, RadiusY = 5}};
25 | var diff = Diff.Calculate(null, img).ToArray();
26 |
27 | diff[0].ShouldBeOfType();
28 | }
29 |
30 | [Fact]
31 | public void Clip_property_not_set_if_value_is_identical()
32 | {
33 | var img = new Image {Clip = new EllipseGeometry {Center = new xf.Point(10, 10), RadiusX = 3, RadiusY = 5}};
34 | var diff = Diff.Calculate(img,
35 | new Image {Clip = new EllipseGeometry {Center = new xf.Point(10, 10), RadiusX = 3, RadiusY = 5}});
36 | diff.ShouldBeEmpty();
37 | }
38 |
39 | [Fact]
40 | public void diffing_Path_as_Clip()
41 | {
42 | var img = new Image {Clip = new PathGeometry("M 10,100 C 100,0 200,200 300,100")};
43 | var diff = Diff.Calculate(null, img);
44 |
45 | diff.First().ShouldBeOfType();
46 | }
47 | }
--------------------------------------------------------------------------------
/src/Behaviors.cs:
--------------------------------------------------------------------------------
1 | namespace Laconic;
2 |
3 | public abstract class Behavior : Element
4 | {
5 | public static readonly xf.BindableProperty ValueProperty = xf.BindableProperty.Create(
6 | "Value",
7 | typeof(object),
8 | typeof(xf.Behavior));
9 | }
10 |
11 | public abstract class Behavior : Behavior where T : Xamarin.Forms.VisualElement
12 | {
13 | protected Behavior()
14 | {
15 | }
16 |
17 | protected Behavior(object? value) => SetValue(ValueProperty, value);
18 |
19 | protected internal override xf.BindableObject CreateView() => new BehaviorAdapter(this);
20 |
21 | protected internal abstract void OnAttachedTo(T bindable);
22 |
23 | protected internal virtual void OnDetachingFrom(T bindable)
24 | {
25 | }
26 |
27 | protected internal virtual void OnValuesUpdated(object value)
28 | {
29 | }
30 | }
31 |
32 | class BehaviorAdapter : xf.Behavior where T : xf.VisualElement
33 | {
34 | readonly Behavior _internal;
35 |
36 | public BehaviorAdapter(Behavior behavior) => _internal = behavior;
37 |
38 | bool _isAttached;
39 |
40 | protected override void OnAttachedTo(T bindable)
41 | {
42 | _internal.OnAttachedTo(bindable);
43 | _isAttached = true;
44 | }
45 |
46 | protected override void OnDetachingFrom(T bindable)
47 | {
48 | _internal.OnDetachingFrom(bindable);
49 | _isAttached = false;
50 | }
51 |
52 | protected override void OnPropertyChanged(string propertyName)
53 | {
54 | if (propertyName == Behavior.ValueProperty.PropertyName && _isAttached) {
55 | _internal.OnValuesUpdated(GetValue(Behavior.ValueProperty));
56 | }
57 | }
58 | }
--------------------------------------------------------------------------------
/demo/droid/Resources/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
8 |
35 |
41 |
--------------------------------------------------------------------------------
/maps/demo/droid/Resources/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
8 |
35 |
41 |
--------------------------------------------------------------------------------
/test/BasicTests.cs:
--------------------------------------------------------------------------------
1 | namespace Laconic.Tests;
2 |
3 | public class BasicTests
4 | {
5 | [Fact]
6 | public void visual_element_equality()
7 | {
8 | ((Label?) null == null).ShouldBeTrue();
9 |
10 | new Label { Text = "a" }.Equals(new Label { Text = "a" }).ShouldBeTrue();
11 | new Label { Text = "a" }.Equals(new Label { Text = "b" }).ShouldBeFalse();
12 |
13 | (new Label {Text = "a"} == new Label {Text = "a"}).ShouldBeTrue();
14 | (new Label {Text = "a"} == new Label {Text = "b"}).ShouldBeFalse();
15 |
16 | (new Label() == null).ShouldBeFalse();
17 | }
18 |
19 | [Fact]
20 | public void key_casting_equality()
21 | {
22 | (new Key(1) == 1).ShouldBeTrue();
23 | (new Key(1L) == 1L).ShouldBeTrue();
24 | (new Key("a") == "a").ShouldBeTrue();
25 |
26 | (new Key(2) == 1).ShouldBeFalse();
27 | (new Key(2L) == 1L).ShouldBeFalse();
28 | (new Key("a") == "b").ShouldBeFalse();
29 | }
30 |
31 | [Fact(Skip="TODO: removed when refactoring LocalContext")]
32 | public void throw_on_setting_child_key_twice() =>
33 | Should.Throw(() =>
34 | {
35 | var _ = new StackLayout {["1"] = new Label(), ["1"] = new Label()};
36 | }).Message.ShouldBe("An item with the same key has already been added. Key: 1");
37 |
38 | [Fact]
39 | public void Signal_deconstruction()
40 | {
41 | var g1 = new Signal("a", "b");
42 | var (a, b) = g1;
43 | a.ShouldBe("a");
44 | b.ShouldBe("b");
45 |
46 | // TODO: Does it even make sense?
47 | var g2 = new Signal("c");
48 | var (c, d) = g2;
49 | c.ShouldBe("c");
50 | d.ShouldBeNull();
51 | }
52 | }
--------------------------------------------------------------------------------
/src/ImageSource.cs:
--------------------------------------------------------------------------------
1 | using System.IO;
2 | using System.Reflection;
3 |
4 | namespace Laconic;
5 |
6 | public abstract class ImageSource : Element
7 | {
8 | public static ImageSource FromFile(string file) => new FileImageSource {File = file};
9 |
10 | public static ImageSource FromResource(string resource, Type resolvingType) =>
11 | FromResource(resource, resolvingType.GetTypeInfo().Assembly);
12 |
13 | public static ImageSource FromResource(string resource, Assembly? sourceAssembly = null) =>
14 | throw new NotImplementedException();
15 |
16 | public static ImageSource FromStream(Func stream) => throw new NotImplementedException();
17 |
18 | public static ImageSource FromUri(Uri uri) => new UriImageSource {Uri = uri};
19 |
20 | public static implicit operator ImageSource?(string? source)
21 | {
22 | // Taken from xf.ImageSourceConverter.ConvertFromInvariantString(source)
23 | if (source == null)
24 | return null;
25 |
26 | return Uri.TryCreate(source, UriKind.Absolute, out var uri) && uri.Scheme != "file"
27 | ? FromUri(uri)
28 | : FromFile(source);
29 | }
30 | }
31 |
32 | public partial class FontImageSource : ImageSource
33 | {
34 | protected internal override xf.BindableObject CreateView() => new xf.FontImageSource();
35 | }
36 |
37 | public partial class FileImageSource : ImageSource
38 | {
39 | protected internal override xf.BindableObject CreateView() => new xf.FileImageSource();
40 | }
41 |
42 | public class UriImageSource : ImageSource
43 | {
44 | public Uri Uri {
45 | get => GetValue(xf.UriImageSource.UriProperty);
46 | set => SetValue(xf.UriImageSource.UriProperty, value);
47 | }
48 |
49 | protected internal override xf.BindableObject CreateView() => new xf.UriImageSource();
50 | }
--------------------------------------------------------------------------------
/src/GridViewList.cs:
--------------------------------------------------------------------------------
1 | namespace Laconic;
2 |
3 | public class GridViewList : ViewList
4 | {
5 | internal (int Row, int Column, int RowSpan, int ColumnSpan) GetPositioning(Key key) => _positioning[key];
6 |
7 | internal void SetPositioning(Key key, int row, int column, int rowSpan, int columnSpan) =>
8 | _positioning[key] = (row, column, rowSpan, columnSpan);
9 |
10 | readonly Dictionary _positioning = new();
11 |
12 | public void Add(Key key, View? blueprint, int row = 0, int column = 0, int rowSpan = 1, int columnSpan = 1)
13 | {
14 | if (blueprint != null) {
15 | base.Add(key, blueprint);
16 | SetPositioning(key, row, column, rowSpan, columnSpan);
17 | }
18 | }
19 |
20 | public static implicit operator GridViewList(Dictionary<(Key Key, int Row, int Column), View?> source)
21 | {
22 | var res = new GridViewList();
23 | foreach (var item in source.Where(x => x.Value != null))
24 | {
25 | res.Add(item.Key.Key, item.Value);
26 | res.SetPositioning(item.Key.Key, item.Key.Row, item.Key.Column, 1, 1);
27 | }
28 |
29 | return res;
30 | }
31 | }
32 |
33 | public class ItemsViewList : ViewList
34 | {
35 | internal readonly Dictionary ReuseKeys = new();
36 |
37 | public View? this[string reuseKey, Key key]
38 | {
39 | set
40 | {
41 | if (value != null) {
42 | base[key] = value;
43 | ReuseKeys[key] = reuseKey;
44 | }
45 | }
46 | }
47 |
48 | public void Add(string reuseKey, Key key, View? view)
49 | {
50 | if (view != null) {
51 | Add(key, view);
52 | ReuseKeys[key] = reuseKey;
53 | }
54 | }
55 | }
--------------------------------------------------------------------------------
/demo/app/BehaviorPage.cs:
--------------------------------------------------------------------------------
1 | using System.ComponentModel;
2 | using Xamarin.Forms;
3 |
4 | namespace Laconic.Demo;
5 |
6 | class AnimateTextSize : Behavior
7 | {
8 | protected override void OnAttachedTo(xf.Label bindable) => bindable.PropertyChanged += OnPropertyChanged;
9 |
10 | protected override void OnDetachingFrom(xf.Label bindable) => bindable.PropertyChanged -= OnPropertyChanged;
11 |
12 | void OnPropertyChanged(object sender, PropertyChangedEventArgs e)
13 | {
14 | if (e.PropertyName == xf.Label.TextProperty.PropertyName) {
15 | var lbl = (xf.Label)sender;
16 | lbl.Animate("text-size", s => lbl.Scale = s, 5.0, 1.0);
17 | }
18 | }
19 | }
20 |
21 | static class BehaviorPage
22 | {
23 | public static View Content() => Element.WithContext("behavior", ctx => {
24 | var (count, setCount) = ctx.UseLocalState(0);
25 | return new Grid {
26 | ["lbl"] = new Label {
27 | Text = count.ToString(),
28 | FontSize = 50,
29 | Behaviors = {["anim"] = new AnimateTextSize()},
30 | HorizontalOptions = LayoutOptions.Center,
31 | VerticalOptions = LayoutOptions.Center,
32 | }, ["btn", row: 1] = new Button {
33 | Text = "Update",
34 | Clicked = () => setCount(count + 1),
35 | TextColor = Color.White,
36 | FontSize = 20,
37 | BackgroundColor = Color.Coral,
38 | BorderColor = Color.Chocolate,
39 | BorderWidth = 2,
40 | CornerRadius = 20,
41 | Margin = (0, 30),
42 | Padding = (30, 0),
43 | HorizontalOptions = LayoutOptions.Center,
44 | VerticalOptions = LayoutOptions.End,
45 | }
46 | };
47 | });
48 | }
--------------------------------------------------------------------------------
/maps/demo/app/SignalsStateReducers.cs:
--------------------------------------------------------------------------------
1 | using System.Linq;
2 |
3 | namespace Laconic.Maps.Demo
4 | {
5 | enum FeatureSwitch
6 | {
7 | IsShowingUser,
8 | TrafficEnabled,
9 | HasScrollEnabled,
10 | HasZoomEnabled
11 | }
12 |
13 | record City(string Name, int Population, Polygon[] Boundaries, bool IsShownOnMap);
14 | record Features(bool IsShowingUser, bool TrafficEnabled, bool HasScrollEnabled, bool HasZoomEnabled);
15 | record State(MapType MapType, Features Features, City[] Cities);
16 |
17 | static class Reducers
18 | {
19 | static Features Features(Features features, Signal signal) => signal switch {
20 | (FeatureSwitch.IsShowingUser, bool val) => features with { IsShowingUser = val },
21 | (FeatureSwitch.TrafficEnabled, bool val) => features with { TrafficEnabled = val },
22 | (FeatureSwitch.HasScrollEnabled, bool val) => features with { HasScrollEnabled = val },
23 | (FeatureSwitch.HasZoomEnabled, bool val) => features with { HasZoomEnabled = val },
24 | _ => features
25 | };
26 |
27 | static City[] Cities(City[] cities, Signal signal) => signal switch {
28 | ("toggle-city", string cityName) => cities
29 | .Select(c => c.Name == cityName ? c with { IsShownOnMap = ! c.IsShownOnMap } : c).ToArray(),
30 | _ => cities
31 | };
32 |
33 | public static State Main(State state, Signal signal) => signal switch {
34 | (MapType t, _) => state with { MapType = t },
35 | _ => state with { Features = Features(state.Features, signal), Cities = Cities(state.Cities, signal) }
36 | };
37 |
38 | }
39 | }
40 |
41 | // Records won't work without this
42 | namespace System.Runtime.CompilerServices
43 | {
44 | sealed class IsExternalInit
45 | {
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/demo/app/SwipeViewPage.cs:
--------------------------------------------------------------------------------
1 | namespace Laconic.Demo;
2 |
3 | static class SwipeViewPage
4 | {
5 | public static VisualElement Content() => Element.WithContext(ctx => {
6 | var (text, setText) = ctx.UseLocalState("");
7 |
8 | return new StackLayout {
9 | [0] = new SwipeView {
10 | HeightRequest = 50,
11 | VerticalOptions = LayoutOptions.CenterAndExpand,
12 | LeftItems = {
13 | ["fav"] = new SwipeItem {
14 | Text = "Favourite",
15 | IconImageSource = new FontImageSource {Size = 15, FontFamily = "IconFont", Glyph = "\uf02e"},
16 | BackgroundColor = Color.LightGreen,
17 | Invoked = _ => setText("Favourite")
18 | },
19 | ["del"] = new SwipeItem {
20 | Text = "Delete",
21 | IconImageSource = new FontImageSource {Size = 15, FontFamily = "IconFont", Glyph = "\uf2ed"},
22 | BackgroundColor = Color.LightPink,
23 | Invoked = _ => setText("Delete")
24 | }
25 | },
26 | Content = new Grid {
27 | HeightRequest = 60,
28 | WidthRequest = 300,
29 | [0] = new Label {
30 | Text = "Swipe right",
31 | HorizontalOptions = LayoutOptions.Center,
32 | VerticalOptions = LayoutOptions.Center
33 | }
34 | }
35 | },
36 | ["lbl"] = text == "" ? null : new Label {
37 | Text = $"Invoked: {text}",
38 | HorizontalOptions = LayoutOptions.Center,
39 | Margin = (0, 50)
40 | }
41 | };
42 | });
43 | }
--------------------------------------------------------------------------------
/src/Key.cs:
--------------------------------------------------------------------------------
1 | namespace Laconic;
2 |
3 | public class Key : IEquatable
4 | {
5 | readonly object _value;
6 |
7 | // TODO: overloads for allowed types
8 | public Key(object value) => _value = value;
9 |
10 | public bool Equals(Key other) => _value.Equals(other._value);
11 |
12 | public override bool Equals(object other) => other is Key key && this.Equals(key);
13 |
14 | public override int GetHashCode() => _value.GetHashCode();
15 |
16 | public override string ToString() => _value.ToString();
17 |
18 | public static bool operator ==(Key lhs, Key rhs) => lhs._value.Equals(rhs._value);
19 | public static bool operator !=(Key lhs, Key rhs) => !lhs._value.Equals(rhs._value);
20 |
21 | // Must provide implicit conversions for all primitive types allowed as keys (string, int, long, guid)
22 | // later: DateTime, DateTimeOffset
23 |
24 | public static bool operator ==(Key lhs, string rhs) => lhs._value is string && lhs._value.Equals(rhs);
25 | public static bool operator !=(Key lhs, string rhs) => !(lhs._value is string && lhs._value.Equals(rhs));
26 | public static implicit operator Key(string value) => new(value);
27 |
28 | public static bool operator ==(Key lhs, int rhs) => lhs._value is int && lhs._value.Equals(rhs);
29 | public static bool operator !=(Key lhs, int rhs) => !(lhs._value is int && lhs._value.Equals(rhs));
30 | public static implicit operator Key(int value) => new(value);
31 |
32 | public static bool operator ==(Key lhs, long rhs) => lhs._value.Equals(rhs);
33 | public static bool operator !=(Key lhs, long rhs) => !lhs._value.Equals(rhs);
34 | public static implicit operator Key(long value) => new(value);
35 |
36 | public static bool operator ==(Key lhs, Guid rhs) => lhs._value.Equals(rhs);
37 | public static bool operator !=(Key lhs, Guid rhs) => !lhs._value.Equals(rhs);
38 | public static implicit operator Key(Guid value) => new(value);
39 | }
--------------------------------------------------------------------------------
/demo/app/DynamicGrid.cs:
--------------------------------------------------------------------------------
1 | namespace Laconic.Demo;
2 |
3 | class GridSignal : Signal
4 | {
5 | public GridSignal(string type, double value) : base(type, value)
6 | {
7 | }
8 | }
9 |
10 | static class DynamicGrid
11 | {
12 | public static (int Rows, int Columns) Reducer((int Rows, int Columns) state, GridSignal signal) => signal switch
13 | {
14 | ("r", double val) => ((int)Math.Round(val), state.Columns),
15 | ("c", double val) => (state.Rows, (int)Math.Round(val)),
16 | _ => throw new NotImplementedException()
17 | };
18 |
19 | public static StackLayout Content((int Rows, int Columns) state) => new() {
20 | BackgroundColor = Color.Bisque,
21 | Padding = 50,
22 | ["rowsLabel"] = new Label {Text = "Rows:"},
23 | ["rowsSlider"] =
24 | new Slider {Maximum = 10, Minimum = 2, Value = state.Rows, ValueChanged = e => new GridSignal("r", e.NewValue)},
25 | ["colsLabel"] = new Label {Text = "Columns:"},
26 | ["colsSlider"] = new Slider
27 | {
28 | Maximum = 6, Minimum = 2, Value = state.Columns, ValueChanged = e => new GridSignal("c", e.NewValue)
29 | },
30 | ["grid"] = new Grid
31 | {
32 | Children = (from r in Enumerable.Range(0, state.Rows)
33 | from c in Enumerable.Range(0, state.Columns)
34 | select (Row: r, Column: c))
35 | .ToGridViewList(x => (x.Row * state.Columns + x.Column, x.Row, x.Column),
36 | x => new Label
37 | {
38 | Text = $"R{x.Row}C{x.Column}",
39 | VerticalTextAlignment = TextAlignment.Center,
40 | HorizontalTextAlignment = TextAlignment.Center,
41 | HeightRequest = 70,
42 | BackgroundColor = Color.Chocolate,
43 | TextColor = Color.White
44 | })
45 | },
46 | };
47 | }
--------------------------------------------------------------------------------
/codegen/test/SignalTests.cs:
--------------------------------------------------------------------------------
1 | using Xunit;
2 | using Shouldly;
3 |
4 | namespace Laconic.CodeGeneration.Tests
5 | {
6 | public class Signal
7 | {
8 | public readonly object? Payload;
9 | readonly object? _p1;
10 | readonly object? _p2;
11 |
12 | public Signal(object? payload) => (Payload, _p1, _p2) = (payload, payload, null);
13 | public Signal(object p1, object? p2) => (Payload, _p1, _p2) = (p1, p1, p2);
14 |
15 | public void Deconstruct(out object? p1, out object? p2) => (p1, p2) = (_p1, _p2);
16 |
17 | public override string ToString() => $"{GetType()}: {_p1?.ToString()} {_p2?.ToString()}";
18 | }
19 |
20 | public class Signal : Signal
21 | {
22 | public Signal(T payload) : base(payload)
23 | {
24 | }
25 |
26 | public new T Payload => (T) base.Payload!;
27 | }
28 |
29 | [Signals]
30 | interface __MySignal
31 | {
32 | Signal NoPayload();
33 | Signal WithOneParam(string id);
34 | Signal WithTwoParams(int id, string name);
35 | Signal WithThreeParams(int id, string first, string second);
36 | }
37 |
38 | public class SignalTests
39 | {
40 | [Fact]
41 | public void Signal_generation_works()
42 | {
43 | var noPayload = new NoPayload();
44 |
45 | noPayload.ShouldBeAssignableTo();
46 | noPayload.ShouldBeAssignableTo();
47 | noPayload.Payload.ShouldBeNull();
48 |
49 | var twoParams = new WithTwoParams(1, "one");
50 | twoParams.Id.ShouldBe(1);
51 | twoParams.Name.ShouldBe("one");
52 | var (id, name) = twoParams;
53 | id.ShouldBe(1);
54 | name.ShouldBe("one");
55 |
56 | var threeParams = new WithThreeParams(1, "one", "two");
57 |
58 | threeParams.Id.ShouldBe(1);
59 | threeParams.First.ShouldBe("one");
60 | threeParams.Second.ShouldBe("two");
61 | }
62 | }
63 | }
--------------------------------------------------------------------------------
/src/Pages.cs:
--------------------------------------------------------------------------------
1 | namespace Laconic;
2 |
3 | // TODO: MenuItem etc.
4 | public class ToolbarItem : Element
5 | {
6 | public ImageSource IconImageSource {
7 | init => SetValue(xf.MenuItem.IconImageSourceProperty, value);
8 | }
9 |
10 | public string Text {
11 | init => SetValue(xf.MenuItem.TextProperty, value);
12 | }
13 |
14 | public Func Clicked
15 | {
16 | init => SetEvent(nameof(Clicked), value,
17 | (ctl, handler) => ctl.Clicked += handler,
18 | (ctl, handler) => ctl.Clicked -= handler);
19 | }
20 |
21 | protected internal override xf.BindableObject CreateView() => new xf.ToolbarItem();
22 | }
23 |
24 | public interface Page
25 | {
26 |
27 | }
28 |
29 | public abstract partial class Page : VisualElement, Page where T : Xamarin.Forms.Page, new()
30 | {
31 | public IDictionary ToolbarItems { get; } = new Dictionary();
32 | }
33 |
34 | public class ContentPage : Page, IContentHost
35 | {
36 | public View? Content { get; set; }
37 |
38 | public string BackButtonTitle {
39 | init => ProvidedValues[xf.NavigationPage.BackButtonTitleProperty] = value;
40 | }
41 |
42 | // TODO:
43 | // HasNavigationBarProperty
44 | //HasBackButtonProperty
45 | // TitleIconImageSourceProperty
46 | // IconColorProperty
47 | public View TitleView {
48 | init => ProvidedValues[xf.NavigationPage.TitleViewProperty] = value;
49 | }
50 |
51 | public override string ToString() => "ContentPage{" + Content + "}";
52 | }
53 |
54 | public partial class FlyoutPage : Page
55 | {
56 | public Element? Flyout { set; get; }
57 | public Element? Detail { set; get; }
58 |
59 | public Func BackButtonPressed {
60 | init => SetEvent(nameof(BackButtonPressed), value,
61 | (ctl, handler) => ctl.BackButtonPressed += handler,
62 | (ctl, handler) => ctl.BackButtonPressed -= handler);
63 | }
64 | }
--------------------------------------------------------------------------------
/codegen/README.md:
--------------------------------------------------------------------------------
1 | # Code generation of Records and Unions for C# 8
2 |
3 | Install the package from NuGet: [Laconic.CodeGeneration](https://www.nuget.org/packages/Laconic.CodeGeneration/0.9.3-beta). This is compile-time only dependency.
4 |
5 | ## Records
6 |
7 | Write an interface marked with `RecordsAttribute`, the interface name doesn't matter:
8 |
9 | ```csharp
10 | [Records]
11 | public interface MyRecords
12 | {
13 | record User(string firstName, string lastName);
14 | }
15 | ```
16 |
17 | **What's generated:** each method in the interface becomes an immutable partial class, with a constructor, value equality and `With` method. Each parameter in the method becomes a property:
18 |
19 | ```csharp
20 | var johnny = new User("Johnny", "Smith");
21 | Console.WriteLine(johnny.FirstName); // prints "Johnny"
22 | Console.WriteLine(johhny == new User("Johnny", "Smith")); // prints "true"
23 |
24 | var grownUp = johnny.With(firstName: "John"); // values that are not supplied are copied from the original object
25 | Console.WriteLine(johnny == grownUp); // prints "false"
26 | ```
27 |
28 | ## Unions
29 |
30 | Write an interface marked with `UnionAttribute` using one or more underscores as suffix or prefix.
31 |
32 | ```csharp
33 | [Union]
34 | interface __Shape__
35 | {
36 | record Circle(double radius);
37 | record Rectangle(double length, double width);
38 | }
39 | ```
40 |
41 | **What's generated:** The name of the interface with stripped underscores becomes the name of your union, each method becomes a record implementing the union interface:
42 |
43 | ```csharp
44 | var circle = new Circle(10);
45 | var rect = new Rectangle(10, 20);
46 |
47 | double Area(Shape shape) => shape switch {
48 | Circle c => Math.PI * c.Radius * c.Radius,
49 | Rectangle r => r.Length * r.Height,
50 | _ => throw new NotImplementedException()
51 | };
52 | ```
53 |
54 | ## Credits
55 |
56 | The inspiration (and some code) came from the excellent [LanguageExt](https://github.com/louthy/language-ext) project. Check it out!
57 |
58 | All the heavy lifting is done by [CodeGeneration.Roslyn](https://github.com/AArnott/CodeGeneration.Roslyn).
59 |
--------------------------------------------------------------------------------
/src/GridDiff.cs:
--------------------------------------------------------------------------------
1 | namespace Laconic;
2 |
3 | enum GridPositionChangeType
4 | {
5 | Row,
6 | Column,
7 | RowSpan,
8 | ColumnSpan
9 | }
10 |
11 | static class GridDiff
12 | {
13 | public static DiffOperation? CalculateRowDefinitionsDiff(
14 | Grid? existingGrid, Grid newGrid) => (existingGrid, newGrid) switch {
15 | (_, null) => null,
16 | (null, _) => new RowDefinitionsChange(newGrid.RowDefinitions.ToArray()),
17 | var (e, n) when n.RowDefinitions.Equals(e.RowDefinitions) => null,
18 | (_, _) => new RowDefinitionsChange(newGrid.RowDefinitions.ToArray())
19 | };
20 |
21 | public static DiffOperation? CalculateColumnDefinitionsDiff(
22 | Grid? existingGrid, Grid newGrid) => (existingGrid, newGrid) switch {
23 | (_, null) => null,
24 | (null, _) => new ColumnDefinitionsChange(newGrid.ColumnDefinitions.ToArray()),
25 | var (e, n) when n.ColumnDefinitions.Equals(e.ColumnDefinitions) => null,
26 | (_, _) => new ColumnDefinitionsChange(newGrid.ColumnDefinitions.ToArray())
27 | };
28 |
29 | public static IEnumerable CalculatePositioningInGrid(Key key, GridViewList? existingList,
30 | GridViewList newList)
31 | {
32 | var existingPos = (Row: 0, Column: 0, RowSpan: 1, ColumnSpan: 1);
33 | if (existingList != null && existingList.ContainsKey(key))
34 | existingPos = existingList.GetPositioning(key);
35 |
36 | var newPos = newList.GetPositioning(key);
37 |
38 | if (newPos.Row != existingPos.Row)
39 | yield return new GridPositionChange(GridPositionChangeType.Row, newPos.Row);
40 |
41 | if (newPos.Column != existingPos.Column)
42 | yield return new GridPositionChange(GridPositionChangeType.Column, newPos.Column);
43 |
44 | if (newPos.RowSpan != existingPos.RowSpan)
45 | yield return new GridPositionChange(GridPositionChangeType.RowSpan, newPos.RowSpan);
46 |
47 | if (newPos.ColumnSpan != existingPos.ColumnSpan)
48 | yield return new GridPositionChange(GridPositionChangeType.ColumnSpan, newPos.ColumnSpan);
49 | }
50 | }
--------------------------------------------------------------------------------
/src/Size.cs:
--------------------------------------------------------------------------------
1 | namespace Laconic;
2 |
3 | public struct Size
4 | {
5 | readonly double _width;
6 |
7 | readonly double _height;
8 |
9 | public static readonly Size Zero = new();
10 |
11 | public bool IsZero {
12 | get {
13 | if (_width == 0.0) {
14 | return _height == 0.0;
15 | }
16 |
17 | return false;
18 | }
19 | }
20 |
21 | public double Width => _width;
22 |
23 | public double Height => _height;
24 |
25 | public Size(double width, double height)
26 | {
27 | if (double.IsNaN(width)) {
28 | throw new ArgumentException("NaN is not a valid value for width");
29 | }
30 |
31 | if (double.IsNaN(height)) {
32 | throw new ArgumentException("NaN is not a valid value for height");
33 | }
34 |
35 | _width = width;
36 | _height = height;
37 | }
38 |
39 | public static Size operator +(Size s1, Size s2) => new(s1._width + s2._width, s1._height + s2._height);
40 |
41 | public static Size operator -(Size s1, Size s2) => new(s1._width - s2._width, s1._height - s2._height);
42 |
43 | public static Size operator *(Size s1, double value) => new(s1._width * value, s1._height * value);
44 |
45 | public static bool operator ==(Size s1, Size s2)
46 | {
47 | if (s1._width == s2._width) {
48 | return s1._height == s2._height;
49 | }
50 |
51 | return false;
52 | }
53 |
54 | public static bool operator !=(Size s1, Size s2)
55 | {
56 | if (s1._width == s2._width) {
57 | return s1._height != s2._height;
58 | }
59 |
60 | return true;
61 | }
62 |
63 | public static explicit operator Point(Size size) => new(size.Width, size.Height);
64 |
65 | bool Equals(Size other) => _width.Equals(other._width) && _height.Equals(other._height);
66 |
67 | public override bool Equals(object obj) => obj switch {
68 | null => false,
69 | Size size => Equals(size),
70 | _ => false
71 | };
72 |
73 | public override int GetHashCode() => (_width.GetHashCode() * 397) ^ _height.GetHashCode();
74 | }
--------------------------------------------------------------------------------
/test/MiddlewareTests.cs:
--------------------------------------------------------------------------------
1 | namespace Laconic.Tests;
2 |
3 | public class MiddlewareTests
4 | {
5 | [Fact]
6 | public void middleware_is_called()
7 | {
8 | var binder = Binder.Create("", (s, g) => s);
9 | var isCalled = false;
10 | binder.UseMiddleware((context, next) =>
11 | {
12 | isCalled = true;
13 | return next(context);
14 | });
15 |
16 | binder.ProcessSignal(new Signal("_"));
17 |
18 | isCalled.ShouldBeTrue();
19 | }
20 |
21 | [Fact]
22 | public void middleware_modifies_state_before_reducer()
23 | {
24 | var binder = Binder.Create("initial", (s, g) => s + " - reducer");
25 | binder.UseMiddleware((context, next) =>
26 | {
27 | var modified = context.WithState(context.State + " - modified");
28 | return next(modified);
29 | });
30 |
31 | binder.ProcessSignal(new Signal("_"));
32 |
33 | binder.State.ShouldBe("initial - modified - reducer");
34 | }
35 |
36 | [Fact]
37 | public void middleware_modifies_state_after_reducer()
38 | {
39 | var binder = Binder.Create("initial", (s, g) => "reducer");
40 | binder.UseMiddleware((context, next) =>
41 | {
42 | var ctx = next(context);
43 | return ctx.WithState(ctx.State + " - modified");
44 | });
45 |
46 | binder.ProcessSignal(new Signal("_"));
47 |
48 | binder.State.ShouldBe("reducer - modified");
49 | }
50 |
51 | [Fact]
52 | public void middleware_can_be_chained()
53 | {
54 | var binder = Binder.Create("initial", (s, g) => "reducer");
55 | binder.UseMiddleware((context, next) =>
56 | {
57 | var ctx = next(context);
58 | return ctx.WithState(ctx.State + " - outer");
59 | });
60 | binder.UseMiddleware((context, next) =>
61 | {
62 | var ctx = next(context);
63 | return ctx.WithState(ctx.State + " - inner");
64 | });
65 |
66 | binder.ProcessSignal(new Signal("_"));
67 |
68 | binder.State.ShouldBe("reducer - inner - outer");
69 | }
70 | }
--------------------------------------------------------------------------------
/codegen/CodeGen.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio 15
4 | VisualStudioVersion = 15.0.26124.0
5 | MinimumVisualStudioVersion = 15.0.26124.0
6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CodeGen.Tests", "test\CodeGen.Tests.csproj", "{1CCADF8C-19AB-4D3C-B049-9ED8D730405B}"
7 | EndProject
8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CodeGen.Attributes", "src\attributes\CodeGen.Attributes.csproj", "{35932F8A-46DA-4E46-890F-61FED0421CC3}"
9 | EndProject
10 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CodeGen.Generators", "src\generators\CodeGen.Generators.csproj", "{865FE8BA-00B4-46DB-BC82-B5FA0D985A5F}"
11 | EndProject
12 | Global
13 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
14 | Debug|Any CPU = Debug|Any CPU
15 | Release|Any CPU = Release|Any CPU
16 | EndGlobalSection
17 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
18 | {1CCADF8C-19AB-4D3C-B049-9ED8D730405B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
19 | {1CCADF8C-19AB-4D3C-B049-9ED8D730405B}.Debug|Any CPU.Build.0 = Debug|Any CPU
20 | {1CCADF8C-19AB-4D3C-B049-9ED8D730405B}.Release|Any CPU.ActiveCfg = Release|Any CPU
21 | {1CCADF8C-19AB-4D3C-B049-9ED8D730405B}.Release|Any CPU.Build.0 = Release|Any CPU
22 | {35932F8A-46DA-4E46-890F-61FED0421CC3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
23 | {35932F8A-46DA-4E46-890F-61FED0421CC3}.Debug|Any CPU.Build.0 = Debug|Any CPU
24 | {35932F8A-46DA-4E46-890F-61FED0421CC3}.Release|Any CPU.ActiveCfg = Release|Any CPU
25 | {35932F8A-46DA-4E46-890F-61FED0421CC3}.Release|Any CPU.Build.0 = Release|Any CPU
26 | {865FE8BA-00B4-46DB-BC82-B5FA0D985A5F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
27 | {865FE8BA-00B4-46DB-BC82-B5FA0D985A5F}.Debug|Any CPU.Build.0 = Debug|Any CPU
28 | {865FE8BA-00B4-46DB-BC82-B5FA0D985A5F}.Release|Any CPU.ActiveCfg = Release|Any CPU
29 | {865FE8BA-00B4-46DB-BC82-B5FA0D985A5F}.Release|Any CPU.Build.0 = Release|Any CPU
30 | EndGlobalSection
31 | GlobalSection(SolutionProperties) = preSolution
32 | HideSolutionNode = FALSE
33 | EndGlobalSection
34 | GlobalSection(ExtensibilityGlobals) = postSolution
35 | SolutionGuid = {18247316-A7C2-4598-82BF-F9103F2C6084}
36 | EndGlobalSection
37 | EndGlobal
38 |
--------------------------------------------------------------------------------
/src/Thickness.cs:
--------------------------------------------------------------------------------
1 | namespace Laconic;
2 |
3 | public readonly struct Thickness
4 | {
5 | public static implicit operator Thickness(double uniformSize) => new(uniformSize);
6 |
7 | public static implicit operator Thickness((double horizontalSize, double verticalSize) values)
8 | => new(values.horizontalSize, values.verticalSize);
9 |
10 | public static implicit operator Thickness((double left, double top, double right, double bottom) values)
11 | => new(values.left, values.top, values.right, values.bottom);
12 |
13 | public double Left { get; }
14 | public double Top { get; }
15 | public double Right { get; }
16 | public double Bottom { get; }
17 |
18 | Thickness(double uniformSize) => this = new Thickness(uniformSize, uniformSize, uniformSize, uniformSize);
19 |
20 | Thickness(double horizontalSize, double verticalSize) =>
21 | this = new Thickness(horizontalSize, verticalSize, horizontalSize, verticalSize);
22 |
23 | Thickness(double left, double top, double right, double bottom)
24 | {
25 | this = default(Thickness);
26 | Left = left;
27 | Top = top;
28 | Right = right;
29 | Bottom = bottom;
30 | }
31 |
32 | bool Equals(Thickness other)
33 | {
34 | if (Left.Equals(other.Left) && Top.Equals(other.Top) && Right.Equals(other.Right)) {
35 | return Bottom.Equals(other.Bottom);
36 | }
37 |
38 | return false;
39 | }
40 |
41 | public override bool Equals(object obj) => obj switch {
42 | null => false,
43 | Thickness thickness => Equals(thickness),
44 | _ => false
45 | };
46 |
47 | public override int GetHashCode() =>
48 | (((((Left.GetHashCode() * 397) ^ Top.GetHashCode()) * 397) ^ Right.GetHashCode()) * 397) ^
49 | Bottom.GetHashCode();
50 |
51 | public static bool operator ==(Thickness left, Thickness right) => left.Equals(right);
52 |
53 | public static bool operator !=(Thickness left, Thickness right) => !left.Equals(right);
54 |
55 | public void Deconstruct(out double left, out double top, out double right, out double bottom)
56 | {
57 | left = Left;
58 | top = Top;
59 | right = Right;
60 | bottom = Bottom;
61 | }
62 | }
--------------------------------------------------------------------------------
/src/Point.cs:
--------------------------------------------------------------------------------
1 | using System.Globalization;
2 |
3 | namespace Laconic;
4 |
5 | public readonly struct Point : IConvert
6 | {
7 | public static Point Zero;
8 |
9 | public double X { get; }
10 |
11 | public double Y { get; }
12 |
13 | public bool IsEmpty {
14 | get {
15 | if (X == 0.0) {
16 | return Y == 0.0;
17 | }
18 |
19 | return false;
20 | }
21 | }
22 |
23 | public override string ToString() =>
24 | $"{{X={X.ToString(CultureInfo.InvariantCulture)} Y={Y.ToString(CultureInfo.InvariantCulture)}}}";
25 |
26 | public object ToNative() => new Xamarin.Forms.Point(X, Y);
27 |
28 | public Point(double x, double y)
29 | {
30 | this = default(Point);
31 | X = x;
32 | Y = y;
33 | }
34 |
35 | public Point(Size sz)
36 | {
37 | this = default(Point);
38 | X = sz.Width;
39 | Y = sz.Height;
40 | }
41 |
42 | public override bool Equals(object o)
43 | {
44 | if (!(o is Point)) {
45 | return false;
46 | }
47 |
48 | return this == (Point) o;
49 | }
50 |
51 | public override int GetHashCode() => X.GetHashCode() ^ (Y.GetHashCode() * 397);
52 |
53 | public static explicit operator Size(Point pt) => new(pt.X, pt.Y);
54 |
55 | public static Point operator +(Point pt, Size sz) => new(pt.X + sz.Width, pt.Y + sz.Height);
56 |
57 | public static Point operator -(Point pt, Size sz) => new(pt.X - sz.Width, pt.Y - sz.Height);
58 |
59 | public static bool operator ==(Point ptA, Point ptB)
60 | {
61 | if (ptA.X == ptB.X) {
62 | return ptA.Y == ptB.Y;
63 | }
64 |
65 | return false;
66 | }
67 |
68 | public static bool operator !=(Point ptA, Point ptB)
69 | {
70 | if (ptA.X == ptB.X) {
71 | return ptA.Y != ptB.Y;
72 | }
73 |
74 | return true;
75 | }
76 |
77 | public static implicit operator Point((double X, double Y) value) => new(value.X, value.Y);
78 |
79 | public double Distance(Point other) => Math.Sqrt(Math.Pow(X - other.X, 2.0) + Math.Pow(Y - other.Y, 2.0));
80 |
81 | public void Deconstruct(out double x, out double y)
82 | {
83 | x = X;
84 | y = Y;
85 | }
86 | }
--------------------------------------------------------------------------------
/binding-report.md:
--------------------------------------------------------------------------------
1 | ## Not Used
2 |
3 | AdaptiveTrigger
4 |
5 | Application
6 |
7 | Cell
8 |
9 | CompareStateTrigger
10 |
11 | ContentPresenter
12 |
13 | DataTrigger
14 |
15 | DeviceStateTrigger
16 |
17 | EntryCell
18 |
19 | EventTrigger
20 |
21 | FlexLayout
22 |
23 | GestureElement
24 |
25 | GroupableItemsView
26 |
27 | HtmlWebViewSource
28 |
29 | ImageCell
30 |
31 | MasterDetailPage
32 |
33 | MultiPage`1
34 |
35 | MultiTrigger
36 |
37 | NavigableElement
38 |
39 | OrientationStateTrigger
40 |
41 | SearchHandler
42 |
43 | StateTrigger
44 |
45 | StateTriggerBase
46 |
47 | SwitchCell
48 |
49 | TableSectionBase
50 |
51 | TableSectionBase`1
52 |
53 | TableView
54 |
55 | TemplatedItemsList`2
56 |
57 | TemplatedPage
58 |
59 | TemplatedView
60 |
61 | TextCell
62 |
63 | Trigger
64 |
65 | TriggerBase
66 |
67 | UrlWebViewSource
68 |
69 | ViewCell
70 |
71 | WebViewSource
72 |
73 | ## Not Implemented
74 |
75 | AppLinkEntry
76 |
77 | ArcSegment
78 |
79 | BackButtonBehavior
80 |
81 | BaseMenuItem
82 |
83 | BaseShellItem
84 |
85 | Behavior
86 |
87 | Behavior`1
88 |
89 | BezierSegment
90 |
91 | ClickGestureRecognizer
92 |
93 | CompositeTransform
94 |
95 | FlyoutItem
96 |
97 | GeometryGroup
98 |
99 | GridItemsLayout
100 |
101 | ItemsLayout
102 |
103 | LinearItemsLayout
104 |
105 | LineSegment
106 |
107 | MatrixTransform
108 |
109 | Menu
110 |
111 | MenuItem
112 |
113 | OpenGLView
114 |
115 | PanGestureRecognizer
116 |
117 | PathGeometry
118 |
119 | PathSegment
120 |
121 | PinchGestureRecognizer
122 |
123 | PolyBezierSegment
124 |
125 | PolyLineSegment
126 |
127 | PolyQuadraticBezierSegment
128 |
129 | QuadraticBezierSegment
130 |
131 | RectangleGeometry
132 |
133 | RelativeLayout
134 |
135 | RotateTransform
136 |
137 | ScaleTransform
138 |
139 | Shell
140 |
141 | ShellContent
142 |
143 | ShellGroupItem
144 |
145 | ShellItem
146 |
147 | ShellSection
148 |
149 | SkewTransform
150 |
151 | StreamImageSource
152 |
153 | SwipeGestureRecognizer
154 |
155 | SwipeItemView
156 |
157 | Tab
158 |
159 | TabBar
160 |
161 | Transform
162 |
163 | TransformGroup
164 |
165 | TranslateTransform
166 |
167 | ## Undefined
168 |
169 | DragGestureRecognizer
170 |
171 | DropGestureRecognizer
172 |
173 | RoundRectangleGeometry
174 |
175 |
--------------------------------------------------------------------------------
/maps/demo/app/CityLoader.cs:
--------------------------------------------------------------------------------
1 | using System.IO;
2 | using System.Linq;
3 | using System.Reflection;
4 | using GeoJSON.Net.Feature;
5 | using Newtonsoft.Json;
6 |
7 | namespace Laconic.Maps.Demo
8 | {
9 | class CityLoader
10 | {
11 | public static City[] Load()
12 | {
13 | var assembly = typeof(City).GetTypeInfo().Assembly;
14 | // data source: https://github.com/drei01/geojson-world-cities
15 | var stream = assembly.GetManifestResourceStream(assembly.GetName().Name + ".largest-cities.geojson");
16 | var json = "";
17 | using (var reader = new StreamReader (stream))
18 | {
19 | json = reader.ReadToEnd ();
20 | }
21 |
22 | var source = JsonConvert.DeserializeObject (json);
23 |
24 | static Polygon[] GetPolygons(FeatureCollection col, string cityName)
25 | {
26 | // Query
27 |
28 | var sourcePolygons =
29 | from feat in col.Features
30 | from prop in feat.Properties
31 | from poly in (feat.Geometry as GeoJSON.Net.Geometry.Polygon).Coordinates
32 | where prop.Value.Equals(cityName.ToUpper())
33 | select poly.Coordinates;
34 |
35 | // Transform
36 |
37 | return sourcePolygons.Select(
38 | p => new Polygon {
39 | FillColor = (0, 0, 255, 25),
40 | StrokeColor = Color.Blue,
41 | StrokeWidth = 1,
42 | Geopath = p.Select(x => new Position(x.Latitude, x.Longitude)).ToList()
43 | }).ToArray();
44 | }
45 |
46 | return new[] {
47 | new City("Tokyo", 37_400_068, GetPolygons(source, "Tokyo"), false),
48 | new City("Delhi", 28_514_000, GetPolygons(source, "New Delhi"), false), // can't find boundaries for Delhi
49 | new City("Shanghai", 25_582_000, GetPolygons(source, "Shanghai"), false),
50 | new City("São Paulo", 21_650_000, GetPolygons(source, "Sao Paulo"), false),
51 | new City("Mexico City", 21_650_000, GetPolygons(source, "Mexico City"), false)
52 | };
53 | }
54 |
55 | }
56 | }
--------------------------------------------------------------------------------
/maps/src/Map.cs:
--------------------------------------------------------------------------------
1 | using System.Collections;
2 | using xf = Xamarin.Forms;
3 |
4 | namespace Laconic.Maps
5 | {
6 | public enum MapType
7 | {
8 | Street,
9 | Satellite,
10 | Hybrid
11 | }
12 |
13 | public enum PinType
14 | {
15 | Generic,
16 | Place,
17 | SavedPin,
18 | SearchResult
19 | }
20 |
21 | public class Map : View
22 | {
23 | public Map()
24 | {
25 | // TODO: generic parameter or casting should not be necessary
26 | ElementLists.Add(nameof(Pins), map => (IList) map.Pins);
27 | ElementLists.Add(nameof(MapElements), map => (IList) map.MapElements);
28 | }
29 |
30 | public ElementList Pins => ElementLists[nameof(Pins)];
31 |
32 | public ElementList MapElements {
33 | get => ElementLists[nameof(MapElements)];
34 | set => ElementLists[nameof(MapElements)] = value;
35 | }
36 |
37 | public MapSpan? VisibleRegion {
38 | set => SetValue(nameof(VisibleRegion), value, map => {
39 | if (value == null)
40 | return;
41 | map.MoveToRegion(new xf.Maps.MapSpan(
42 | new xf.Maps.Position(value.Center.Latitude, value.Center.Longitude), value.LatitudeDegrees,
43 | value.LongitudeDegrees));
44 | });
45 | }
46 |
47 | public MapType MapType {
48 | get => (MapType) GetValue(xf.Maps.Map.MapTypeProperty);
49 | set => SetValue(xf.Maps.Map.MapTypeProperty, (xf.Maps.MapType) value);
50 | }
51 |
52 | public bool IsShowingUser {
53 | get => GetValue(xf.Maps.Map.IsShowingUserProperty);
54 | set => SetValue(xf.Maps.Map.IsShowingUserProperty, value);
55 | }
56 |
57 | public bool TrafficEnabled {
58 | get => GetValue(xf.Maps.Map.TrafficEnabledProperty);
59 | set => SetValue(xf.Maps.Map.TrafficEnabledProperty, value);
60 | }
61 |
62 | public bool HasScrollEnabled {
63 | get => GetValue(xf.Maps.Map.HasScrollEnabledProperty);
64 | set => SetValue(xf.Maps.Map.HasScrollEnabledProperty, value);
65 | }
66 |
67 | public bool HasZoomEnabled {
68 | get => GetValue(xf.Maps.Map.HasZoomEnabledProperty);
69 | set => SetValue(xf.Maps.Map.HasZoomEnabledProperty, value);
70 | }
71 | }
72 | }
--------------------------------------------------------------------------------
/demo/app/GroupedCollectionView.cs:
--------------------------------------------------------------------------------
1 | using ChanceNET;
2 |
3 | namespace Laconic.Demo;
4 |
5 | static class GroupedCollectionView
6 | {
7 | static View SectionHeaderRow(string text) => new Grid
8 | {
9 | Padding = (15, 0),
10 | HeightRequest = 40,
11 | ["letter"] = new Label
12 | {
13 | Text = text,
14 | FontSize = 18,
15 | FontAttributes = FontAttributes.Bold,
16 | BackgroundColor = Color.Chocolate,
17 | TextColor = Color.White,
18 | WidthRequest = 40,
19 | HorizontalOptions = LayoutOptions.Start,
20 | VerticalTextAlignment = TextAlignment.Center,
21 | HorizontalTextAlignment = TextAlignment.Center,
22 | },
23 | ["underline"] = new BoxView
24 | {
25 | BackgroundColor = Color.Chocolate, HeightRequest = 2, VerticalOptions = LayoutOptions.End
26 | }
27 | };
28 |
29 | static View ItemRow(string name, string phone) => new StackLayout
30 | {
31 | Orientation = StackOrientation.Horizontal,
32 | Padding = (30, 0),
33 | HeightRequest = 30,
34 | ["name"] = new Label {Text = name},
35 | ["phone"] = new Label
36 | {
37 | Text = phone, TextColor = Color.Gray, HorizontalOptions = LayoutOptions.EndAndExpand
38 | }
39 | };
40 |
41 | // A helper function that converts a sequence "Alice", "Bob"
42 | // to "A", "Alice", "B", "Bob" and creates corresponding blueprints
43 | static IEnumerable<(string ReuseKey, string Key, View View)> GroupedItems(IEnumerable state)
44 | {
45 | var grouped = state.ToLookup(x => x.LastName.Substring(0, 1), x => x);
46 | foreach (var group in grouped.OrderBy(x => x.Key))
47 | {
48 | yield return ("header", group.Key, SectionHeaderRow(group.Key.ToUpper()));
49 | foreach (var item in group.OrderBy(x => x.LastName))
50 | yield return ("item", item.GetHashCode().ToString(),
51 | ItemRow(item.LastName + ", " + item.FirstName, item.Phone));
52 | }
53 | }
54 |
55 | public static Person[] InitialState()
56 | {
57 | var chance = new Chance();
58 | return Enumerable.Range(1, 200).Select(_ => chance.Person()).ToArray();
59 | }
60 |
61 | public static StackLayout Content(IEnumerable state) => new() {
62 | ["list"] = new CollectionView
63 | {
64 | Items = GroupedItems(state).ToItemsList(x => x.ReuseKey, x => x.Key, x => x.View)
65 | }
66 | };
67 | }
--------------------------------------------------------------------------------
/maps/src/Distance.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace Laconic.Maps
4 | {
5 | /// Struct that encapsulates a distance (natively stored as a double of meters).
6 | /// To be added.
7 | public struct Distance
8 | {
9 | public Distance(double meters) => this.Meters = meters;
10 |
11 | public double Meters { get; }
12 |
13 | public double Miles => this.Meters / 1609.344;
14 |
15 | public double Kilometers => this.Meters / 1000.0;
16 |
17 | public static Distance FromMiles(double miles)
18 | {
19 | if (miles < 0.0)
20 | miles = 0.0;
21 | return new Distance(miles * 1609.344);
22 | }
23 |
24 | public static Distance FromMeters(double meters)
25 | {
26 | if (meters < 0.0)
27 | meters = 0.0;
28 | return new Distance(meters);
29 | }
30 |
31 | public static Distance FromKilometers(double kilometers)
32 | {
33 | if (kilometers < 0.0)
34 | kilometers = 0.0;
35 | return new Distance(kilometers * 1000.0);
36 | }
37 |
38 | public static Distance BetweenPositions(Position position1, Position position2)
39 | {
40 | var radians1 = position1.Latitude.ToRadians();
41 | var radians2 = position1.Longitude.ToRadians();
42 | var radians3 = position2.Latitude.ToRadians();
43 | var radians4 = position2.Longitude.ToRadians();
44 | var num1 = Math.Sin((radians3 - radians1) / 2.0);
45 | var num2 = num1 * num1;
46 | var num3 = radians2;
47 | var num4 = Math.Sin((radians4 - num3) / 2.0);
48 | var num5 = num4 * num4;
49 | var d = num2 + Math.Cos(radians1) * Math.Cos(radians3) * num5;
50 | return FromKilometers(12742.0 * Math.Atan2(Math.Sqrt(d), Math.Sqrt(1.0 - d)));
51 | }
52 |
53 | public bool Equals(Distance other) => this.Meters.Equals(other.Meters);
54 |
55 | public override bool Equals(object? obj) => obj is Distance other && Equals(other);
56 |
57 | public override int GetHashCode() => Meters.GetHashCode();
58 |
59 | public static bool operator ==(Distance left, Distance right) => left.Equals(right);
60 |
61 | public static bool operator !=(Distance left, Distance right) => !left.Equals(right);
62 | }
63 |
64 | static class GeographyUtils
65 | {
66 | public static double ToRadians(this double degrees) => degrees * Math.PI / 180.0;
67 |
68 | public static double ToDegrees(this double radians) => radians / Math.PI * 180.0;
69 | }
70 |
71 | }
--------------------------------------------------------------------------------
/src/ElementListDiff.cs:
--------------------------------------------------------------------------------
1 | namespace Laconic;
2 |
3 | static class ElementListDiff
4 | {
5 | public static ListOperation[] Calculate(IDictionary? existingItems,
6 | IDictionary newItems)
7 | {
8 | var res = new List();
9 |
10 | if (existingItems == null || existingItems.Count == 0) {
11 | foreach (var (key, el) in newItems.Where(p => p.Value != null)) {
12 | var childOps = Diff.Calculate(null, el).ToArray();
13 | // TODO: Refactor. Reuse key shouldn't be necessary when it's not used
14 | res.Add(new AddChild(key, "", res.Count, el, childOps));
15 | }
16 | }
17 | else {
18 | var listDiff = new ListDiff(
19 | existingItems.Where(x => x.Value != null).Select(x => x.Key),
20 | newItems.Where(p => p.Value != null).Select(p => p.Key));
21 |
22 | var index = 0;
23 | foreach (var action in listDiff.Actions) {
24 | if (action.ActionType == ListDiffActionType.Add) {
25 | var newItem = newItems[action.DestinationItem];
26 | var childOps = Diff.Calculate(null, newItem).ToArray();
27 | res.Add(new AddChild(action.DestinationItem, "TODO: Refactor this", index, newItem!, childOps));
28 | index++;
29 | }
30 | else if (action.ActionType == ListDiffActionType.Remove) {
31 | res.Add(new RemoveChild(index));
32 | }
33 | else {
34 | var existingView = existingItems[action.SourceItem];
35 | var newView = newItems[action.SourceItem];
36 | if (existingView == null) {
37 | var items = Diff.Calculate(null, newView).ToArray();
38 | res.Add(new AddChild(action.SourceItem, "TODO: Refactor this", index, newView, items));
39 | }
40 | else if (existingView.GetType() != newView.GetType()) {
41 | var ops = Diff.Calculate(null, newView).ToArray();
42 | res.Add(new ReplaceChild(index, newView, ops));
43 | }
44 | else {
45 | var patch = Diff.Calculate(existingView, newView).ToArray();
46 | if (patch.Any())
47 | res.Add(new UpdateChild(action.DestinationItem, index, newView, patch));
48 | }
49 |
50 | index++;
51 | }
52 | }
53 | }
54 |
55 | return res.ToArray();
56 | }
57 | }
--------------------------------------------------------------------------------
/src/CornerRadius.cs:
--------------------------------------------------------------------------------
1 | namespace Laconic;
2 |
3 | public readonly struct CornerRadius : IConvert
4 | {
5 | readonly bool _isParameterized;
6 |
7 | public double TopLeft { get; }
8 | public double TopRight { get; }
9 | public double BottomLeft { get; }
10 | public double BottomRight { get; }
11 |
12 | CornerRadius(double uniformRadius) : this(uniformRadius, uniformRadius, uniformRadius, uniformRadius)
13 | {
14 | }
15 |
16 | CornerRadius(double topLeft, double topRight, double bottomLeft, double bottomRight)
17 | {
18 | _isParameterized = true;
19 |
20 | TopLeft = topLeft;
21 | TopRight = topRight;
22 | BottomLeft = bottomLeft;
23 | BottomRight = bottomRight;
24 | }
25 |
26 | public static implicit operator CornerRadius(double uniformRadius) => new(uniformRadius);
27 |
28 | public static implicit operator CornerRadius(
29 | (double topLeft, double topRight, double bottomLeft, double bottomRight) values)
30 | => new(values.topLeft, values.topRight, values.bottomLeft, values.bottomRight);
31 |
32 | bool Equals(CornerRadius other)
33 | {
34 | if (!_isParameterized && !other._isParameterized)
35 | return true;
36 |
37 | return TopLeft == other.TopLeft && TopRight == other.TopRight && BottomLeft == other.BottomLeft &&
38 | BottomRight == other.BottomRight;
39 | }
40 |
41 | public override bool Equals(object? obj)
42 | {
43 | if (ReferenceEquals(null, obj))
44 | return false;
45 |
46 | return obj is CornerRadius cornerRadius && Equals(cornerRadius);
47 | }
48 |
49 | public override int GetHashCode()
50 | {
51 | unchecked {
52 | var hashCode = TopLeft.GetHashCode();
53 | hashCode = (hashCode * 397) ^ TopRight.GetHashCode();
54 | hashCode = (hashCode * 397) ^ BottomLeft.GetHashCode();
55 | hashCode = (hashCode * 397) ^ BottomRight.GetHashCode();
56 | return hashCode;
57 | }
58 | }
59 |
60 | public static bool operator ==(CornerRadius left, CornerRadius right) => left.Equals(right);
61 |
62 | public static bool operator !=(CornerRadius left, CornerRadius right) => !left.Equals(right);
63 |
64 | public void Deconstruct(out double topLeft, out double topRight, out double bottomLeft, out double bottomRight)
65 | {
66 | topLeft = TopLeft;
67 | topRight = TopRight;
68 | bottomLeft = BottomLeft;
69 | bottomRight = BottomRight;
70 | }
71 |
72 | object IConvert.ToNative() =>
73 | _isParameterized
74 | ? new Xamarin.Forms.CornerRadius(TopLeft, TopRight, BottomLeft, BottomRight)
75 | : new Xamarin.Forms.CornerRadius(TopLeft);
76 | }
--------------------------------------------------------------------------------
/cli-template/src/ios/Resources/LaunchScreen.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
--------------------------------------------------------------------------------
/test/GridTests.cs:
--------------------------------------------------------------------------------
1 | namespace Laconic.Tests;
2 |
3 | public class GridTests
4 | {
5 | [Fact]
6 | public void should_set_RowDefinitions_from_string()
7 | {
8 | var grid = new Grid {RowDefinitions = "*, 2*, Auto, 30"};
9 |
10 | grid.RowDefinitions.Count.ShouldBe(4);
11 | grid.RowDefinitions[0].Height.ShouldBe(xf.GridLength.Star);
12 | grid.RowDefinitions[1].Height.ShouldBe(new xf.GridLength(2, xf.GridUnitType.Star));
13 | grid.RowDefinitions[2].Height.ShouldBe(xf.GridLength.Auto);
14 | grid.RowDefinitions[3].Height.ShouldBe(new xf.GridLength(30));
15 | }
16 |
17 | [Fact]
18 | public void should_set_ColumnDefinitions_from_string()
19 | {
20 | var grid = new Grid {ColumnDefinitions = "2*, Auto, 30"};
21 |
22 | grid.ColumnDefinitions.Count.ShouldBe(3);
23 | grid.ColumnDefinitions[0].Width.ShouldBe(new xf.GridLength(2, xf.GridUnitType.Star));
24 | grid.ColumnDefinitions[1].Width.ShouldBe(xf.GridLength.Auto);
25 | grid.ColumnDefinitions[2].Width.ShouldBe(new xf.GridLength(30));
26 | }
27 |
28 | [Fact]
29 | public void should_create_rows_in_real_view()
30 | {
31 | var grid = new xf.Grid();
32 | Patch.Apply(grid, Diff.Calculate(null, new Grid {RowDefinitions = "*, 2*, Auto, 30"}), _ => { });
33 |
34 | grid.RowDefinitions.Count.ShouldBe(4);
35 | grid.RowDefinitions[0].Height.ShouldBe(xf.GridLength.Star);
36 | grid.RowDefinitions[1].Height.ShouldBe(new xf.GridLength(2, xf.GridUnitType.Star));
37 | grid.RowDefinitions[2].Height.ShouldBe(xf.GridLength.Auto);
38 | grid.RowDefinitions[3].Height.ShouldBe(new xf.GridLength(30));
39 | }
40 |
41 | [Fact]
42 | public void should_set_ColumnDefinitions_in_real_view()
43 | {
44 | var grid = new xf.Grid();
45 | Patch.Apply(grid, Diff.Calculate(null, new Grid {ColumnDefinitions = "2*, Auto, 30"}), _ => { });
46 |
47 | grid.ColumnDefinitions.Count.ShouldBe(3);
48 | grid.ColumnDefinitions[0].Width.ShouldBe(new xf.GridLength(2, xf.GridUnitType.Star));
49 | grid.ColumnDefinitions[1].Width.ShouldBe(xf.GridLength.Auto);
50 | grid.ColumnDefinitions[2].Width.ShouldBe(new xf.GridLength(30));
51 | }
52 |
53 | [Fact]
54 | public void should_set_ColumnDefinitions_in_real_view2()
55 | {
56 | var grid = new xf.Grid();
57 | Patch.Apply(grid, Diff.Calculate(null, new Grid {ColumnDefinitions = "Auto, *, Auto"}), _ => { });
58 |
59 | grid.ColumnDefinitions.Count.ShouldBe(3);
60 | grid.ColumnDefinitions[0].Width.ShouldBe(xf.GridLength.Auto);
61 | grid.ColumnDefinitions[1].Width.ShouldBe(xf.GridLength.Star);
62 | grid.ColumnDefinitions[2].Width.ShouldBe(xf.GridLength.Auto);
63 | }
64 | }
--------------------------------------------------------------------------------
/test/BehaviorTests.cs:
--------------------------------------------------------------------------------
1 | namespace Laconic.Tests;
2 |
3 | public class BehaviorTests
4 | {
5 | class TestBehavior : Behavior
6 | {
7 | public bool IsAttached;
8 | readonly string _text;
9 | xf.Label? _label;
10 |
11 | public TestBehavior(string text) : base(text) => _text = text;
12 |
13 | protected internal override void OnValuesUpdated(object value) => _label!.Text = (string)value;
14 |
15 | protected internal override void OnAttachedTo(xf.Label bindable)
16 | {
17 | _label = bindable;
18 | IsAttached = true;
19 | bindable.Text = _text;
20 | }
21 |
22 | protected internal override void OnDetachingFrom(xf.Label bindable) => IsAttached = false;
23 | }
24 |
25 | [Fact]
26 | public void XF_Behavior_is_added_and_removed()
27 | {
28 | var real = new xf.Label();
29 | var testBehavior = new TestBehavior("");
30 | var originalBlueprint = new Label {Behaviors = {[0] = testBehavior}};
31 |
32 | Patch.Apply(real, Diff.Calculate(null, originalBlueprint), _ => { });
33 |
34 | real.Behaviors.Count.ShouldBe(1);
35 | testBehavior.IsAttached.ShouldBeTrue();
36 |
37 | Patch.Apply(real, Diff.Calculate(originalBlueprint, new Label()), _ => { });
38 |
39 | real.Behaviors.Count.ShouldBe(0);
40 | testBehavior.IsAttached.ShouldBeFalse();
41 | }
42 |
43 | [Fact]
44 | public void Behavior_is_reused()
45 | {
46 | var real = new xf.Label();
47 | var originalBlueprint = new Label {Behaviors = {[0] = new TestBehavior("") }};
48 |
49 | Patch.Apply(real, Diff.Calculate(null, originalBlueprint), _ => { });
50 |
51 | real.Behaviors.Count.ShouldBe(1);
52 | var realBehavior = real.Behaviors[0];
53 |
54 | Patch.Apply(real, Diff.Calculate(
55 | originalBlueprint,
56 | new Label{Behaviors = {[0] = new TestBehavior("")}}),
57 | _ => { });
58 |
59 | real.Behaviors[0].ShouldBe(realBehavior);
60 | }
61 |
62 | [Fact]
63 | public void Behavior_updates_view_values()
64 | {
65 | var real = new xf.Label();
66 |
67 | var originalBlueprint = new Label {Behaviors = {[0] = new TestBehavior("")}};
68 | Patch.Apply(real, Diff.Calculate(null, originalBlueprint), _ => { });
69 |
70 | real.Text.ShouldBe("");
71 |
72 | Patch.Apply(real, Diff.Calculate(
73 | originalBlueprint,
74 | new Label{Behaviors = {[0] = new TestBehavior("updated")}}),
75 | _ => { });
76 |
77 | real.Text.ShouldBe("updated");
78 | }
79 | }
--------------------------------------------------------------------------------
/demo/ios/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images": [
3 | {
4 | "scale": "2x",
5 | "size": "20x20",
6 | "idiom": "iphone",
7 | "filename": "Icon40.png"
8 | },
9 | {
10 | "scale": "3x",
11 | "size": "20x20",
12 | "idiom": "iphone",
13 | "filename": "Icon60.png"
14 | },
15 | {
16 | "scale": "2x",
17 | "size": "29x29",
18 | "idiom": "iphone",
19 | "filename": "Icon58.png"
20 | },
21 | {
22 | "scale": "3x",
23 | "size": "29x29",
24 | "idiom": "iphone",
25 | "filename": "Icon87.png"
26 | },
27 | {
28 | "scale": "2x",
29 | "size": "40x40",
30 | "idiom": "iphone",
31 | "filename": "Icon80.png"
32 | },
33 | {
34 | "scale": "3x",
35 | "size": "40x40",
36 | "idiom": "iphone",
37 | "filename": "Icon120.png"
38 | },
39 | {
40 | "scale": "2x",
41 | "size": "60x60",
42 | "idiom": "iphone",
43 | "filename": "Icon120.png"
44 | },
45 | {
46 | "scale": "3x",
47 | "size": "60x60",
48 | "idiom": "iphone",
49 | "filename": "Icon180.png"
50 | },
51 | {
52 | "scale": "1x",
53 | "size": "20x20",
54 | "idiom": "ipad",
55 | "filename": "Icon20.png"
56 | },
57 | {
58 | "scale": "2x",
59 | "size": "20x20",
60 | "idiom": "ipad",
61 | "filename": "Icon40.png"
62 | },
63 | {
64 | "scale": "1x",
65 | "size": "29x29",
66 | "idiom": "ipad",
67 | "filename": "Icon29.png"
68 | },
69 | {
70 | "scale": "2x",
71 | "size": "29x29",
72 | "idiom": "ipad",
73 | "filename": "Icon58.png"
74 | },
75 | {
76 | "scale": "1x",
77 | "size": "40x40",
78 | "idiom": "ipad",
79 | "filename": "Icon40.png"
80 | },
81 | {
82 | "scale": "2x",
83 | "size": "40x40",
84 | "idiom": "ipad",
85 | "filename": "Icon80.png"
86 | },
87 | {
88 | "scale": "1x",
89 | "size": "76x76",
90 | "idiom": "ipad",
91 | "filename": "Icon76.png"
92 | },
93 | {
94 | "scale": "2x",
95 | "size": "76x76",
96 | "idiom": "ipad",
97 | "filename": "Icon152.png"
98 | },
99 | {
100 | "scale": "2x",
101 | "size": "83.5x83.5",
102 | "idiom": "ipad",
103 | "filename": "Icon167.png"
104 | },
105 | {
106 | "scale": "1x",
107 | "size": "1024x1024",
108 | "idiom": "ios-marketing",
109 | "filename": "Icon1024.png"
110 | }
111 | ],
112 | "properties": {},
113 | "info": {
114 | "version": 1,
115 | "author": "xcode"
116 | }
117 | }
--------------------------------------------------------------------------------
/codegen/test/RecordTests.cs:
--------------------------------------------------------------------------------
1 | using Laconic.CodeGeneration;
2 | using Xunit;
3 |
4 | namespace Laconic.CodeGeneration.Tests
5 | {
6 | // ReSharper disable UnusedType.Global
7 | // ReSharper disable UnusedMember.Global
8 | [Records]
9 | public interface TestRecords
10 | {
11 | record User(string firstName, string lastName);
12 | record TaggedUser(User user, params string[] tags);
13 | }
14 | // ReSharper restore UnusedType.Global
15 | // ReSharper restore UnusedMember.Global
16 |
17 | public class RecordTests
18 | {
19 | [Fact]
20 | public void parameters_become_property_names()
21 | {
22 | var user = new User("a", "b");
23 |
24 | Assert.Equal("a", user.FirstName);
25 | Assert.Equal("b", user.LastName);
26 | }
27 |
28 | [Fact]
29 | public void With_method_works()
30 | {
31 | var user = new User("a", "b");
32 | var updated = user.With(lastName: "c");
33 |
34 | Assert.Equal("a", updated.FirstName);
35 | Assert.Equal("c", updated.LastName);
36 | }
37 |
38 | [Fact]
39 | public void Deconstruct_method_works()
40 | {
41 | var user = new User("a", "b");
42 | var (first, last) = user;
43 |
44 | Assert.Equal("a", first);
45 | Assert.Equal("b", last);
46 | }
47 |
48 | [Fact]
49 | public void Records_have_structural_equality()
50 | {
51 | Assert.Equal(new User("a", "b"), new User("a", "b"));
52 | Assert.True(((object) new User("a", "b")).Equals((object) new User("a", "b")));
53 |
54 | Assert.NotEqual(new User("a", "b"), new User("a", "c"));
55 |
56 | Assert.True(new User("a", "b") == new User("a", "b"));
57 | Assert.False(new User("a", "b") == new User("a", "c"));
58 | }
59 |
60 | [Fact]
61 | public void Records_define_GetHashCode()
62 | {
63 | var user1 = new User("a", "b");
64 | var user2 = new User("a", "b");
65 | var user3 = new User("a", "c");
66 |
67 | Assert.Equal(user1.GetHashCode(), user2.GetHashCode());
68 | Assert.NotEqual(user1.GetHashCode(), user3.GetHashCode());
69 | }
70 |
71 | [Fact]
72 | public void Records_with_params_arrays_have_structural_equality()
73 | {
74 | Assert.Equal(
75 | new TaggedUser(new User("a", "b"), "t1", "t2"),
76 | new TaggedUser(new User("a", "b"), "t1", "t2"));
77 |
78 | Assert.NotEqual(
79 | new TaggedUser(new User("a", "b"), "t1", "t2"),
80 | new TaggedUser(new User("a", "b"), "t1", "t2", "t3"));
81 | }
82 | }
83 | }
--------------------------------------------------------------------------------
/maps/demo/ios/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images": [
3 | {
4 | "scale": "2x",
5 | "size": "20x20",
6 | "idiom": "iphone",
7 | "filename": "Icon40.png"
8 | },
9 | {
10 | "scale": "3x",
11 | "size": "20x20",
12 | "idiom": "iphone",
13 | "filename": "Icon60.png"
14 | },
15 | {
16 | "scale": "2x",
17 | "size": "29x29",
18 | "idiom": "iphone",
19 | "filename": "Icon58.png"
20 | },
21 | {
22 | "scale": "3x",
23 | "size": "29x29",
24 | "idiom": "iphone",
25 | "filename": "Icon87.png"
26 | },
27 | {
28 | "scale": "2x",
29 | "size": "40x40",
30 | "idiom": "iphone",
31 | "filename": "Icon80.png"
32 | },
33 | {
34 | "scale": "3x",
35 | "size": "40x40",
36 | "idiom": "iphone",
37 | "filename": "Icon120.png"
38 | },
39 | {
40 | "scale": "2x",
41 | "size": "60x60",
42 | "idiom": "iphone",
43 | "filename": "Icon120.png"
44 | },
45 | {
46 | "scale": "3x",
47 | "size": "60x60",
48 | "idiom": "iphone",
49 | "filename": "Icon180.png"
50 | },
51 | {
52 | "scale": "1x",
53 | "size": "20x20",
54 | "idiom": "ipad",
55 | "filename": "Icon20.png"
56 | },
57 | {
58 | "scale": "2x",
59 | "size": "20x20",
60 | "idiom": "ipad",
61 | "filename": "Icon40.png"
62 | },
63 | {
64 | "scale": "1x",
65 | "size": "29x29",
66 | "idiom": "ipad",
67 | "filename": "Icon29.png"
68 | },
69 | {
70 | "scale": "2x",
71 | "size": "29x29",
72 | "idiom": "ipad",
73 | "filename": "Icon58.png"
74 | },
75 | {
76 | "scale": "1x",
77 | "size": "40x40",
78 | "idiom": "ipad",
79 | "filename": "Icon40.png"
80 | },
81 | {
82 | "scale": "2x",
83 | "size": "40x40",
84 | "idiom": "ipad",
85 | "filename": "Icon80.png"
86 | },
87 | {
88 | "scale": "1x",
89 | "size": "76x76",
90 | "idiom": "ipad",
91 | "filename": "Icon76.png"
92 | },
93 | {
94 | "scale": "2x",
95 | "size": "76x76",
96 | "idiom": "ipad",
97 | "filename": "Icon152.png"
98 | },
99 | {
100 | "scale": "2x",
101 | "size": "83.5x83.5",
102 | "idiom": "ipad",
103 | "filename": "Icon167.png"
104 | },
105 | {
106 | "scale": "1x",
107 | "size": "1024x1024",
108 | "idiom": "ios-marketing",
109 | "filename": "Icon1024.png"
110 | }
111 | ],
112 | "properties": {},
113 | "info": {
114 | "version": 1,
115 | "author": "xcode"
116 | }
117 | }
--------------------------------------------------------------------------------
/cli-template/src/ios/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images": [
3 | {
4 | "scale": "2x",
5 | "size": "20x20",
6 | "idiom": "iphone",
7 | "filename": "Icon40.png"
8 | },
9 | {
10 | "scale": "3x",
11 | "size": "20x20",
12 | "idiom": "iphone",
13 | "filename": "Icon60.png"
14 | },
15 | {
16 | "scale": "2x",
17 | "size": "29x29",
18 | "idiom": "iphone",
19 | "filename": "Icon58.png"
20 | },
21 | {
22 | "scale": "3x",
23 | "size": "29x29",
24 | "idiom": "iphone",
25 | "filename": "Icon87.png"
26 | },
27 | {
28 | "scale": "2x",
29 | "size": "40x40",
30 | "idiom": "iphone",
31 | "filename": "Icon80.png"
32 | },
33 | {
34 | "scale": "3x",
35 | "size": "40x40",
36 | "idiom": "iphone",
37 | "filename": "Icon120.png"
38 | },
39 | {
40 | "scale": "2x",
41 | "size": "60x60",
42 | "idiom": "iphone",
43 | "filename": "Icon120.png"
44 | },
45 | {
46 | "scale": "3x",
47 | "size": "60x60",
48 | "idiom": "iphone",
49 | "filename": "Icon180.png"
50 | },
51 | {
52 | "scale": "1x",
53 | "size": "20x20",
54 | "idiom": "ipad",
55 | "filename": "Icon20.png"
56 | },
57 | {
58 | "scale": "2x",
59 | "size": "20x20",
60 | "idiom": "ipad",
61 | "filename": "Icon40.png"
62 | },
63 | {
64 | "scale": "1x",
65 | "size": "29x29",
66 | "idiom": "ipad",
67 | "filename": "Icon29.png"
68 | },
69 | {
70 | "scale": "2x",
71 | "size": "29x29",
72 | "idiom": "ipad",
73 | "filename": "Icon58.png"
74 | },
75 | {
76 | "scale": "1x",
77 | "size": "40x40",
78 | "idiom": "ipad",
79 | "filename": "Icon40.png"
80 | },
81 | {
82 | "scale": "2x",
83 | "size": "40x40",
84 | "idiom": "ipad",
85 | "filename": "Icon80.png"
86 | },
87 | {
88 | "scale": "1x",
89 | "size": "76x76",
90 | "idiom": "ipad",
91 | "filename": "Icon76.png"
92 | },
93 | {
94 | "scale": "2x",
95 | "size": "76x76",
96 | "idiom": "ipad",
97 | "filename": "Icon152.png"
98 | },
99 | {
100 | "scale": "2x",
101 | "size": "83.5x83.5",
102 | "idiom": "ipad",
103 | "filename": "Icon167.png"
104 | },
105 | {
106 | "scale": "1x",
107 | "size": "1024x1024",
108 | "idiom": "ios-marketing",
109 | "filename": "Icon1024.png"
110 | }
111 | ],
112 | "properties": {},
113 | "info": {
114 | "version": 1,
115 | "author": "xcode"
116 | }
117 | }
--------------------------------------------------------------------------------
/cli-template/src/app/App.cs:
--------------------------------------------------------------------------------
1 | // Laconic version of TipCalc example from https://www.mvvmcross.com/documentation/tutorials/tipcalc/the-tip-calc-tutorial?scroll=1892
2 |
3 | using System;
4 | using Laconic;
5 |
6 | namespace Laconic.Template
7 | {
8 | public class App : Xamarin.Forms.Application
9 | {
10 | // Everything the app displays and manipulates is kept in an immutable state:
11 | record State
12 | {
13 | public double SubTotal { get; init; }
14 | public double Generosity { get; init; }
15 | public double Tip => SubTotal * Generosity / 100.0;
16 | }
17 |
18 | // ... and this is a function to modify the state:
19 | // given the current state and a signal, calculate and return the new state:
20 | static State Reducer(State state, Signal signal) => signal switch {
21 | // Signal class provides Deconstruct method with two out params; usual usage is (ID, Payload)
22 | ("subtotal", string newVal) => state with {SubTotal = Double.Parse(newVal)},
23 | ("generosity", double newVal) => state with {Generosity = newVal},
24 | _ => state
25 | };
26 |
27 | // ... given the current state, describe how the entire app's UI must look:
28 | static ContentPage UI(State state) => new() {
29 | Content = new StackLayout {
30 | Margin = 50,
31 | // ...for adding child views use Directory initialization syntax:
32 | ["lbl-sub"] = new Label {Text = "Subtotal"},
33 | ["subtotal"] = new Entry {
34 | Text = state.SubTotal.ToString(),
35 | Keyboard = Keyboard.Numeric,
36 | // ... instead of event subscriptions write lambdas that return an instance of Signal.
37 | // When the user provides input Laconic will call those lambdas:
38 | TextChanged = e => new( /* ID: */"subtotal", /* Payload: */ e.NewTextValue)
39 | },
40 | ["lbl-gen"] = new Label {Text = "Generosity"},
41 | ["slider"] = new Slider {
42 | Maximum = 100,
43 | Value = state.Generosity,
44 | ValueChanged = e => new("generosity", e.NewValue)
45 | },
46 | ["lbl-tip-cap"] = new Label {Text = "Tip to leave"},
47 | ["lbl-tip"] = new Label {Text = state.Tip.ToString()}
48 | }
49 | };
50 |
51 | Binder _binder;
52 |
53 | public App()
54 | {
55 | // ... finally, bind everything together:
56 | _binder = Binder.Create(new State {SubTotal = 100, Generosity = 10}, Reducer);
57 | MainPage = _binder.CreateElement(UI);
58 | }
59 | }
60 | }
61 |
62 |
63 | // Temporary stub: records won't work without this
64 | namespace System.Runtime.CompilerServices
65 | {
66 | sealed class IsExternalInit
67 | {
68 | }
69 | }
--------------------------------------------------------------------------------
/src/ViewList.cs:
--------------------------------------------------------------------------------
1 | namespace Laconic;
2 |
3 | public class ViewList : IDictionary
4 | {
5 | readonly Dictionary _internalStorage = new();
6 |
7 | public ViewList()
8 | {
9 | }
10 |
11 | ViewList(IEnumerable<(Key, View?)> source)
12 | {
13 | foreach (var (key, view) in source)
14 | if (view != null)
15 | _internalStorage.Add(key, view);
16 | }
17 |
18 | public View? this[Key key] {
19 | get => _internalStorage[key];
20 | set {
21 | if (value != null)
22 | _internalStorage[key] = value;
23 | }
24 | }
25 |
26 | public int Count => _internalStorage.Count;
27 |
28 | internal IEnumerable Keys => _internalStorage.Keys;
29 |
30 | ICollection IDictionary.Keys => _internalStorage.Keys;
31 | ICollection IDictionary.Values => throw new NotSupportedException();
32 |
33 | int ICollection>.Count => _internalStorage.Count;
34 |
35 | bool ICollection>.IsReadOnly => throw new NotSupportedException();
36 |
37 | public void Add(Key key, View? value)
38 | {
39 | if (value != null)
40 | _internalStorage.Add(key, value);
41 | }
42 |
43 | void ICollection>.Add(KeyValuePair item) =>
44 | throw new NotSupportedException();
45 |
46 | void ICollection>.Clear() => throw new NotSupportedException();
47 |
48 | bool ICollection>.Contains(KeyValuePair item) =>
49 | throw new NotSupportedException();
50 |
51 | public bool ContainsKey(Key key) => _internalStorage.ContainsKey(key);
52 |
53 | void ICollection>.CopyTo(KeyValuePair[] array, int arrayIndex) =>
54 | _internalStorage.ToArray();
55 |
56 | IEnumerator> IEnumerable>.GetEnumerator() =>
57 | _internalStorage.GetEnumerator();
58 |
59 | IEnumerator IEnumerable.GetEnumerator() => _internalStorage.GetEnumerator();
60 |
61 | bool IDictionary.Remove(Key key) => throw new NotSupportedException();
62 |
63 | bool ICollection>.Remove(KeyValuePair item) =>
64 | throw new NotSupportedException();
65 |
66 | bool IDictionary.TryGetValue(Key key, out View value) => throw new NotSupportedException();
67 |
68 | public static implicit operator ViewList(Dictionary source) => new(source.Select(x => (x.Key, x.Value)));
69 |
70 | public static implicit operator ViewList(Dictionary source) => new(source.Select(x => ((Key) x.Key, x.Value)));
71 |
72 | public static implicit operator ViewList(Dictionary source) => new(source.Select(x => ((Key) x.Key, x.Value)));
73 |
74 | public static implicit operator ViewList(Dictionary source) => new(source.Select(x => ((Key) x.Key, x.Value)));
75 | }
--------------------------------------------------------------------------------
/test/CollectionViewTests.cs:
--------------------------------------------------------------------------------
1 | namespace Laconic.Tests;
2 |
3 | public class CollectionViewTests
4 | {
5 | [Fact]
6 | public void should_create_ItemsSource()
7 | {
8 | var colView = new xf.CollectionView();
9 | Patch.Apply(colView,
10 | Diff.Calculate(null,
11 | new CollectionView
12 | {
13 | Items = {["key1"] = new Label {Text = "One"}, ["key2"] = new Label {Text = "Two"},}
14 | }), _ => { });
15 | var source = (IList) colView.ItemsSource;
16 |
17 | source[0].Blueprint.ShouldBeOfType