Merge branch 'feature/tabcontrol' into develop

This commit is contained in:
polarboy 2024-08-04 17:54:27 +08:00
commit 4010ade7e9
47 changed files with 3967 additions and 615 deletions

View File

@ -7,85 +7,474 @@
xmlns:atom="https://atomui.net"
xmlns:showcase="clr-namespace:AtomUI.Demo.Desktop.ShowCase"
mc:Ignorable="d">
<showcase:ShowCasePanel>
<showcase:ShowCaseItem
Title="Basic"
Description="Default activate first tab.">
<StackPanel Orientation="Vertical" Spacing="20">
<atom:TabStrip>
<atom:TabStripItem>Tab 1</atom:TabStripItem>
<atom:TabStripItem>Tab 2</atom:TabStripItem>
<atom:TabStripItem>Tab 3</atom:TabStripItem>
</atom:TabStrip>
</StackPanel>
</showcase:ShowCaseItem>
<TabControl>
<TabItem Header="TabControl">
<showcase:ShowCasePanel>
<showcase:ShowCaseItem
Title="Basic"
Description="Default activate first tab.">
<StackPanel Orientation="Vertical" Spacing="20">
<atom:TabControl>
<atom:TabItem Header="Tab 1">Content of Tab Pane 1</atom:TabItem>
<atom:TabItem Header="Tab 2">Content of Tab Pane 2</atom:TabItem>
<atom:TabItem Header="Tab 3">Content of Tab Pane 3</atom:TabItem>
</atom:TabControl>
</StackPanel>
</showcase:ShowCaseItem>
<showcase:ShowCaseItem
Title="Disabled"
Description="Disabled a tab.">
<StackPanel Orientation="Vertical" Spacing="20">
<atom:CardTabControl>
<atom:TabItem Header="Tab 1">Content of Tab Pane 1</atom:TabItem>
<atom:TabItem Header="Tab 2" IsEnabled="False">Content of Tab Pane 2</atom:TabItem>
<atom:TabItem Header="Tab 3">Content of Tab Pane 3</atom:TabItem>
</atom:CardTabControl>
<atom:TabControl>
<atom:TabItem Header="Tab 1">Content of Tab Pane 1</atom:TabItem>
<atom:TabItem Header="Tab 2" IsEnabled="False">Content of Tab Pane 2</atom:TabItem>
<atom:TabItem Header="Tab 3">Content of Tab Pane 3</atom:TabItem>
</atom:TabControl>
</StackPanel>
</showcase:ShowCaseItem>
<showcase:ShowCaseItem
Title="Centered"
Description="Centered tabs.">
<StackPanel Orientation="Vertical" Spacing="20">
<atom:TabControl TabAlignmentCenter="True">
<atom:TabItem Header="Tab 1">Content of Tab Pane 1</atom:TabItem>
<atom:TabItem Header="Tab 2">Content of Tab Pane 2</atom:TabItem>
<atom:TabItem Header="Tab 3">Content of Tab Pane 3</atom:TabItem>
</atom:TabControl>
<atom:CardTabControl TabAlignmentCenter="True">
<atom:TabItem Header="Tab 1">Content of Tab Pane 1</atom:TabItem>
<atom:TabItem Header="Tab 2">Content of Tab Pane 2</atom:TabItem>
<atom:TabItem Header="Tab 3">Content of Tab Pane 3</atom:TabItem>
</atom:CardTabControl>
</StackPanel>
</showcase:ShowCaseItem>
<showcase:ShowCaseItem
Title="Icon"
Description="The Tab with Icon.">
<StackPanel Orientation="Vertical" Spacing="20">
<atom:TabControl>
<atom:TabItem Header="Tab 1" Icon="{atom:IconProvider Kind=AppleOutlined}">Content of Tab Pane 1</atom:TabItem>
<atom:TabItem Header="Tab 2" Icon="{atom:IconProvider Kind=AndroidOutlined}">Content of Tab Pane 2</atom:TabItem>
<atom:TabItem Header="Tab 3" Icon="{atom:IconProvider Kind=WechatOutlined}">Content of Tab Pane 3</atom:TabItem>
</atom:TabControl>
<showcase:ShowCaseItem
Title="Disabled"
Description="Disabled a tab.">
<StackPanel Orientation="Vertical" Spacing="20">
<atom:TabStrip>
<atom:TabStripItem>Tab 1</atom:TabStripItem>
<atom:TabStripItem IsEnabled="False">Tab 2</atom:TabStripItem>
<atom:TabStripItem>Tab 3</atom:TabStripItem>
</atom:TabStrip>
</StackPanel>
</showcase:ShowCaseItem>
<showcase:ShowCaseItem
Title="Centered"
Description="Centered tabs.">
<StackPanel Orientation="Vertical" Spacing="20">
<atom:TabStrip TabAlignmentCenter="True">
<atom:TabStripItem>Tab 1</atom:TabStripItem>
<atom:TabStripItem>Tab 2</atom:TabStripItem>
<atom:TabStripItem>Tab 3</atom:TabStripItem>
</atom:TabStrip>
</StackPanel>
</showcase:ShowCaseItem>
<showcase:ShowCaseItem
Title="Icon"
Description="The Tab with Icon.">
<StackPanel Orientation="Vertical" Spacing="20">
<atom:TabStrip>
<atom:TabStripItem Icon="{atom:IconProvider Kind=AppleOutlined}">Tab 1</atom:TabStripItem>
<atom:TabStripItem Icon="{atom:IconProvider Kind=AndroidOutlined}">Tab 2</atom:TabStripItem>
<atom:TabStripItem Icon="{atom:IconProvider Kind=WechatOutlined}">Tab 3</atom:TabStripItem>
</atom:TabStrip>
</StackPanel>
</showcase:ShowCaseItem>
<showcase:ShowCaseItem
Title="Slide"
Description="In order to fit in more tabs, they can slide left and right (or up and down).">
<StackPanel Orientation="Vertical" Spacing="20">
<atom:TabStrip>
<atom:TabStripItem>Tab 1</atom:TabStripItem>
<atom:TabStripItem>Tab 2</atom:TabStripItem>
<atom:TabStripItem>Tab 3</atom:TabStripItem>
<atom:TabStripItem>Tab 4</atom:TabStripItem>
<atom:TabStripItem>Tab 5</atom:TabStripItem>
<atom:TabStripItem>Tab 6</atom:TabStripItem>
<atom:TabStripItem>Tab 7</atom:TabStripItem>
<atom:TabStripItem>Tab 8</atom:TabStripItem>
<atom:TabStripItem>Tab 9</atom:TabStripItem>
<atom:TabStripItem>Tab 10</atom:TabStripItem>
<atom:TabStripItem>Tab 11</atom:TabStripItem>
<atom:TabStripItem>Tab 12</atom:TabStripItem>
<atom:TabStripItem>Tab 13</atom:TabStripItem>
<atom:TabStripItem>Tab 14</atom:TabStripItem>
<atom:TabStripItem>Tab 15</atom:TabStripItem>
<atom:TabStripItem>Tab 16</atom:TabStripItem>
<atom:TabStripItem>Tab 17</atom:TabStripItem>
<atom:TabStripItem>Tab 18</atom:TabStripItem>
<atom:TabStripItem>Tab 19</atom:TabStripItem>
<atom:TabStripItem>Tab 20</atom:TabStripItem>
<atom:TabStripItem>Tab 21</atom:TabStripItem>
<atom:TabStripItem>Tab 22</atom:TabStripItem>
<atom:TabStripItem>Tab 23</atom:TabStripItem>
</atom:TabStrip>
</StackPanel>
</showcase:ShowCaseItem>
</showcase:ShowCasePanel>
</StackPanel>
</showcase:ShowCaseItem>
<showcase:ShowCaseItem
Title="Slide"
Description="In order to fit in more tabs, they can slide left and right (or up and down).">
<StackPanel Orientation="Vertical" Spacing="20">
<atom:TabControl>
<atom:TabItem Header="Tab 1">Content of Tab Pane 1</atom:TabItem>
<atom:TabItem Header="Tab 2">Content of Tab Pane 2</atom:TabItem>
<atom:TabItem Header="Tab 3">Content of Tab Pane 3</atom:TabItem>
<atom:TabItem Header="Tab 4">Content of Tab Pane 4</atom:TabItem>
<atom:TabItem Header="Tab 5">Content of Tab Pane 5</atom:TabItem>
<atom:TabItem Header="Tab 6">Content of Tab Pane 6</atom:TabItem>
<atom:TabItem Header="Tab 7">Content of Tab Pane 7</atom:TabItem>
<atom:TabItem Header="Tab 8">Content of Tab Pane 8</atom:TabItem>
<atom:TabItem Header="Tab 9">Content of Tab Pane 9</atom:TabItem>
<atom:TabItem Header="Tab 10">Content of Tab Pane 10</atom:TabItem>
<atom:TabItem Header="Tab 11">Content of Tab Pane 11</atom:TabItem>
<atom:TabItem Header="Tab 12">Content of Tab Pane 12</atom:TabItem>
<atom:TabItem Header="Tab 13">Content of Tab Pane 13</atom:TabItem>
<atom:TabItem Header="Tab 14">Content of Tab Pane 14</atom:TabItem>
<atom:TabItem Header="Tab 15">Content of Tab Pane 15</atom:TabItem>
<atom:TabItem Header="Tab 16">Content of Tab Pane 16</atom:TabItem>
<atom:TabItem Header="Tab 17">Content of Tab Pane 17</atom:TabItem>
<atom:TabItem Header="Tab 18">Content of Tab Pane 18</atom:TabItem>
<atom:TabItem Header="Tab 19">Content of Tab Pane 19</atom:TabItem>
<atom:TabItem Header="Tab 20">Content of Tab Pane 20</atom:TabItem>
</atom:TabControl>
<atom:CardTabControl>
<atom:TabItem Header="Tab 1">Content of Tab Pane 1</atom:TabItem>
<atom:TabItem Header="Tab 2">Content of Tab Pane 2</atom:TabItem>
<atom:TabItem Header="Tab 3">Content of Tab Pane 3</atom:TabItem>
<atom:TabItem Header="Tab 4">Content of Tab Pane 4</atom:TabItem>
<atom:TabItem Header="Tab 5">Content of Tab Pane 5</atom:TabItem>
<atom:TabItem Header="Tab 6">Content of Tab Pane 6</atom:TabItem>
<atom:TabItem Header="Tab 7">Content of Tab Pane 7</atom:TabItem>
<atom:TabItem Header="Tab 8">Content of Tab Pane 8</atom:TabItem>
<atom:TabItem Header="Tab 9">Content of Tab Pane 9</atom:TabItem>
<atom:TabItem Header="Tab 10">Content of Tab Pane 10</atom:TabItem>
<atom:TabItem Header="Tab 11">Content of Tab Pane 11</atom:TabItem>
<atom:TabItem Header="Tab 12">Content of Tab Pane 12</atom:TabItem>
<atom:TabItem Header="Tab 13">Content of Tab Pane 13</atom:TabItem>
<atom:TabItem Header="Tab 14">Content of Tab Pane 14</atom:TabItem>
<atom:TabItem Header="Tab 15">Content of Tab Pane 15</atom:TabItem>
<atom:TabItem Header="Tab 16">Content of Tab Pane 16</atom:TabItem>
<atom:TabItem Header="Tab 17">Content of Tab Pane 17</atom:TabItem>
<atom:TabItem Header="Tab 18">Content of Tab Pane 18</atom:TabItem>
<atom:TabItem Header="Tab 19">Content of Tab Pane 19</atom:TabItem>
<atom:TabItem Header="Tab 20">Content of Tab Pane 20</atom:TabItem>
</atom:CardTabControl>
</StackPanel>
</showcase:ShowCaseItem>
<showcase:ShowCaseItem
Title="Card type tab"
Description="Another type of Tabs, which doesn't support vertical mode.">
<StackPanel Orientation="Vertical" Spacing="20">
<atom:CardTabControl>
<atom:TabItem Header="Tab 1" Icon="{atom:IconProvider Kind=AppleOutlined}">Content of Tab Pane 1</atom:TabItem>
<atom:TabItem Header="Tab 2" Icon="{atom:IconProvider Kind=AndroidOutlined}">Content of Tab Pane 2</atom:TabItem>
<atom:TabItem Header="Tab 3" Icon="{atom:IconProvider Kind=WechatOutlined}">Content of Tab Pane 3</atom:TabItem>
</atom:CardTabControl>
</StackPanel>
</showcase:ShowCaseItem>
<showcase:ShowCaseItem
Title="Position"
Description="Tab's position: left, right, top or bottom. Will auto switch to top in mobile.">
<StackPanel Orientation="Vertical" Spacing="20">
<StackPanel Orientation="Horizontal" Spacing="5">
<TextBlock VerticalAlignment="Center">Tab position:</TextBlock>
<atom:OptionButtonGroup ButtonStyle="Outline" Name="PositionTabControlOptionGroup">
<atom:OptionButton IsChecked="True">Top</atom:OptionButton>
<atom:OptionButton>Bottom</atom:OptionButton>
<atom:OptionButton>Left</atom:OptionButton>
<atom:OptionButton>Right</atom:OptionButton>
</atom:OptionButtonGroup>
</StackPanel>
<atom:TabControl Name="PositionTabControl"
DockPanel.Dock="{Binding PositionTabControlPlacement}"
TabStripPlacement="{Binding PositionTabControlPlacement}"
Height="300"
Width="400"
HorizontalAlignment="Left"
VerticalAlignment="Top">
<atom:TabItem Header="Tab 1" Icon="{atom:IconProvider Kind=AppleOutlined}">Content of Tab Pane 1</atom:TabItem>
<atom:TabItem Header="Tab 2" Icon="{atom:IconProvider Kind=AndroidOutlined}">Content of Tab Pane 2</atom:TabItem>
<atom:TabItem Header="Tab 3">Content of Tab Pane 3</atom:TabItem>
<atom:TabItem Header="Tab 4" Icon="{atom:IconProvider Kind=WechatOutlined}">Content of Tab Pane 4</atom:TabItem>
<atom:TabItem Header="Tab 5">Content of Tab Pane 5</atom:TabItem>
<atom:TabItem Header="Tab 6">Content of Tab Pane 6</atom:TabItem>
<atom:TabItem Header="Tab 7">Content of Tab Pane 7</atom:TabItem>
<atom:TabItem Header="Tab 8">Content of Tab Pane 8</atom:TabItem>
</atom:TabControl>
</StackPanel>
</showcase:ShowCaseItem>
<showcase:ShowCaseItem
Title="Card Shape Position"
Description="Tab's position: left, right, top or bottom. Will auto switch to top in mobile."
Margin="0, 0, 10, 0">
<StackPanel Orientation="Vertical" Spacing="20">
<StackPanel Orientation="Horizontal" Spacing="5">
<TextBlock VerticalAlignment="Center">Tab position:</TextBlock>
<atom:OptionButtonGroup ButtonStyle="Outline" Name="PositionCardTabControlOptionGroup">
<atom:OptionButton IsChecked="True">Top</atom:OptionButton>
<atom:OptionButton>Bottom</atom:OptionButton>
<atom:OptionButton>Left</atom:OptionButton>
<atom:OptionButton>Right</atom:OptionButton>
</atom:OptionButtonGroup>
</StackPanel>
<atom:CardTabControl Name="PositionCardTabControl"
DockPanel.Dock="{Binding PositionCardTabControlPlacement}"
TabStripPlacement="{Binding PositionCardTabControlPlacement}"
IsShowAddTabButton="True"
Height="300"
Width="500"
HorizontalAlignment="Left"
VerticalAlignment="Top">
<atom:TabItem Header="Tab 1" Icon="{atom:IconProvider Kind=AppleOutlined}">Content of Tab Pane 1</atom:TabItem>
<atom:TabItem Header="Tab 2" Icon="{atom:IconProvider Kind=AndroidOutlined}">Content of Tab Pane 2</atom:TabItem>
<atom:TabItem Header="Tab 3">Content of Tab Pane 3</atom:TabItem>
<atom:TabItem Header="Tab 4" Icon="{atom:IconProvider Kind=WechatOutlined}">Content of Tab Pane 4</atom:TabItem>
<atom:TabItem Header="Tab 5">Content of Tab Pane 5</atom:TabItem>
<atom:TabItem Header="Tab 6">Content of Tab Pane 6</atom:TabItem>
<atom:TabItem Header="Tab 7">Content of Tab Pane 7</atom:TabItem>
<atom:TabItem Header="Tab 8">Content of Tab Pane 8</atom:TabItem>
<atom:TabItem Header="Tab 9">Content of Tab Pane 9</atom:TabItem>
<atom:TabItem Header="Tab 10">Content of Tab Pane 10</atom:TabItem>
<atom:TabItem Header="Tab 11">Content of Tab Pane 11</atom:TabItem>
<atom:TabItem Header="Tab 12">Content of Tab Pane 12</atom:TabItem>
</atom:CardTabControl>
</StackPanel>
</showcase:ShowCaseItem>
<showcase:ShowCaseItem
Title="Size"
Description="Large size tabs are usually used in page header, and small size could be used in Modal.">
<StackPanel Orientation="Vertical" Spacing="20">
<StackPanel Orientation="Horizontal" Spacing="5">
<TextBlock VerticalAlignment="Center">Tab position:</TextBlock>
<atom:OptionButtonGroup ButtonStyle="Outline" Name="SizeTypeTabControlOptionGroup">
<atom:OptionButton>Small</atom:OptionButton>
<atom:OptionButton IsChecked="True">Middle</atom:OptionButton>
<atom:OptionButton>Large</atom:OptionButton>
</atom:OptionButtonGroup>
</StackPanel>
<atom:TabControl SizeType="{Binding SizeTypeTabControl}">
<atom:TabItem Header="Tab 1" Icon="{atom:IconProvider Kind=AppleOutlined}">Content of Tab Pane 1</atom:TabItem>
<atom:TabItem Header="Tab 2" Icon="{atom:IconProvider Kind=AndroidOutlined}">Content of Tab Pane 2</atom:TabItem>
<atom:TabItem Header="Tab 3">Content of Tab Pane 3</atom:TabItem>
<atom:TabItem Header="Tab 4" Icon="{atom:IconProvider Kind=WechatOutlined}">Content of Tab Pane 4</atom:TabItem>
</atom:TabControl>
<atom:CardTabControl SizeType="{Binding SizeTypeTabControl}">
<atom:TabItem Header="Tab 1" Icon="{atom:IconProvider Kind=AppleOutlined}">Content of Tab Pane 1</atom:TabItem>
<atom:TabItem Header="Tab 2" Icon="{atom:IconProvider Kind=AndroidOutlined}">Content of Tab Pane 2</atom:TabItem>
<atom:TabItem Header="Tab 3">Content of Tab Pane 3</atom:TabItem>
<atom:TabItem Header="Tab 4" Icon="{atom:IconProvider Kind=WechatOutlined}">Content of Tab Pane 4</atom:TabItem>
</atom:CardTabControl>
</StackPanel>
</showcase:ShowCaseItem>
<showcase:ShowCaseItem
Title="Add and close tab"
Description="Hide default plus icon, and bind event for customized trigger." Margin="0, 0, 30, 0">
<atom:CardTabControl IsShowAddTabButton="True" Name="AddTabDemoTabControl">
<atom:TabItem Header="Tab 1" Icon="{atom:IconProvider Kind=AppleOutlined}">Content of Tab Pane 1</atom:TabItem>
<atom:TabItem Header="Tab 2" Icon="{atom:IconProvider Kind=AndroidOutlined}">Content of Tab Pane 2</atom:TabItem>
<atom:TabItem Header="Tab 3">Content of Tab Pane 3</atom:TabItem>
<atom:TabItem Header="Tab 4" Icon="{atom:IconProvider Kind=WechatOutlined}">Content of Tab Pane 4</atom:TabItem>
</atom:CardTabControl>
</showcase:ShowCaseItem>
</showcase:ShowCasePanel>
</TabItem>
<TabItem Header="TabStrip">
<showcase:ShowCasePanel>
<showcase:ShowCaseItem
Title="Basic"
Description="Default activate first tab.">
<StackPanel Orientation="Vertical" Spacing="20">
<atom:TabStrip>
<atom:TabStripItem>Tab 1</atom:TabStripItem>
<atom:TabStripItem>Tab 2</atom:TabStripItem>
<atom:TabStripItem>Tab 3</atom:TabStripItem>
</atom:TabStrip>
</StackPanel>
</showcase:ShowCaseItem>
<showcase:ShowCaseItem
Title="Disabled"
Description="Disabled a tab.">
<StackPanel Orientation="Vertical" Spacing="20">
<atom:CardTabStrip>
<atom:TabStripItem>Tab 1</atom:TabStripItem>
<atom:TabStripItem IsEnabled="False" IsClosable="True">Tab 2</atom:TabStripItem>
<atom:TabStripItem>Tab 3</atom:TabStripItem>
</atom:CardTabStrip>
<atom:TabStrip>
<atom:TabStripItem>Tab 1</atom:TabStripItem>
<atom:TabStripItem IsEnabled="False">Tab 2</atom:TabStripItem>
<atom:TabStripItem>Tab 3</atom:TabStripItem>
</atom:TabStrip>
</StackPanel>
</showcase:ShowCaseItem>
<showcase:ShowCaseItem
Title="Centered"
Description="Centered tabs.">
<StackPanel Orientation="Vertical" Spacing="20">
<atom:TabStrip TabAlignmentCenter="True">
<atom:TabStripItem>Tab 1</atom:TabStripItem>
<atom:TabStripItem>Tab 2</atom:TabStripItem>
<atom:TabStripItem>Tab 3</atom:TabStripItem>
</atom:TabStrip>
<atom:CardTabStrip TabAlignmentCenter="True">
<atom:TabStripItem>Tab 1</atom:TabStripItem>
<atom:TabStripItem>Tab 2</atom:TabStripItem>
<atom:TabStripItem>Tab 3</atom:TabStripItem>
</atom:CardTabStrip>
</StackPanel>
</showcase:ShowCaseItem>
<showcase:ShowCaseItem
Title="Icon"
Description="The Tab with Icon.">
<StackPanel Orientation="Vertical" Spacing="20">
<atom:TabStrip>
<atom:TabStripItem Icon="{atom:IconProvider Kind=AppleOutlined}">Tab 1</atom:TabStripItem>
<atom:TabStripItem Icon="{atom:IconProvider Kind=AndroidOutlined}">Tab 2</atom:TabStripItem>
<atom:TabStripItem Icon="{atom:IconProvider Kind=WechatOutlined}">Tab 3</atom:TabStripItem>
</atom:TabStrip>
</StackPanel>
</showcase:ShowCaseItem>
<showcase:ShowCaseItem
Title="Slide"
Description="In order to fit in more tabs, they can slide left and right (or up and down).">
<StackPanel Orientation="Vertical" Spacing="20">
<atom:TabStrip>
<atom:TabStripItem>Tab 1</atom:TabStripItem>
<atom:TabStripItem>Tab 2</atom:TabStripItem>
<atom:TabStripItem>Tab 3</atom:TabStripItem>
<atom:TabStripItem>Tab 4</atom:TabStripItem>
<atom:TabStripItem>Tab 5</atom:TabStripItem>
<atom:TabStripItem>Tab 6</atom:TabStripItem>
<atom:TabStripItem>Tab 7</atom:TabStripItem>
<atom:TabStripItem>Tab 8</atom:TabStripItem>
<atom:TabStripItem>Tab 9</atom:TabStripItem>
<atom:TabStripItem>Tab 10</atom:TabStripItem>
<atom:TabStripItem>Tab 11</atom:TabStripItem>
<atom:TabStripItem>Tab 12</atom:TabStripItem>
<atom:TabStripItem>Tab 13</atom:TabStripItem>
<atom:TabStripItem>Tab 14</atom:TabStripItem>
<atom:TabStripItem>Tab 15</atom:TabStripItem>
<atom:TabStripItem>Tab 16</atom:TabStripItem>
<atom:TabStripItem>Tab 17</atom:TabStripItem>
<atom:TabStripItem>Tab 18</atom:TabStripItem>
<atom:TabStripItem>Tab 19</atom:TabStripItem>
<atom:TabStripItem>Tab 20</atom:TabStripItem>
<atom:TabStripItem>Tab 21</atom:TabStripItem>
<atom:TabStripItem>Tab 22</atom:TabStripItem>
<atom:TabStripItem>Tab 23</atom:TabStripItem>
</atom:TabStrip>
</StackPanel>
</showcase:ShowCaseItem>
<showcase:ShowCaseItem
Title="Card type tab"
Description="Another type of Tabs, which doesn't support vertical mode.">
<StackPanel Orientation="Vertical" Spacing="20">
<atom:CardTabStrip>
<atom:TabStripItem Icon="{atom:IconProvider Kind=AppleOutlined}">Tab 1</atom:TabStripItem>
<atom:TabStripItem Icon="{atom:IconProvider Kind=AndroidOutlined}">Tab 2</atom:TabStripItem>
<atom:TabStripItem Icon="{atom:IconProvider Kind=WechatOutlined}">Tab 3</atom:TabStripItem>
<atom:TabStripItem Icon="{atom:IconProvider Kind=GithubOutlined}" IsClosable="True">Tab 4</atom:TabStripItem>
</atom:CardTabStrip>
</StackPanel>
</showcase:ShowCaseItem>
<showcase:ShowCaseItem
Title="Position"
Description="Tab's position: left, right, top or bottom. Will auto switch to top in mobile.">
<StackPanel Orientation="Vertical" Spacing="20">
<StackPanel Orientation="Horizontal" Spacing="5">
<TextBlock VerticalAlignment="Center">Tab position:</TextBlock>
<atom:OptionButtonGroup ButtonStyle="Outline" Name="PositionTabStripOptionGroup">
<atom:OptionButton IsChecked="True">Top</atom:OptionButton>
<atom:OptionButton>Bottom</atom:OptionButton>
<atom:OptionButton>Left</atom:OptionButton>
<atom:OptionButton>Right</atom:OptionButton>
</atom:OptionButtonGroup>
</StackPanel>
<DockPanel Height="300">
<atom:TabStrip Name="PositionTabStrip"
DockPanel.Dock="{Binding PositionTabStripPlacement}"
TabStripPlacement="{Binding PositionTabStripPlacement}">
<atom:TabStripItem Icon="{atom:IconProvider Kind=AppleOutlined}">Tab 1</atom:TabStripItem>
<atom:TabStripItem Icon="{atom:IconProvider Kind=AndroidOutlined}">Tab 2</atom:TabStripItem>
<atom:TabStripItem>Tab 3</atom:TabStripItem>
<atom:TabStripItem Icon="{atom:IconProvider Kind=GithubOutlined}" IsClosable="True">Tab 4</atom:TabStripItem>
</atom:TabStrip>
<Border HorizontalAlignment="Stretch" VerticalAlignment="Stretch"
Background="rgb(241, 243, 246)">
<TextBlock HorizontalAlignment="Center" VerticalAlignment="Center">Tab Content</TextBlock>
</Border>
</DockPanel>
</StackPanel>
</showcase:ShowCaseItem>
<showcase:ShowCaseItem
Title="Card Shape Position"
Description="Tab's position: left, right, top or bottom. Will auto switch to top in mobile."
Margin="0, 0, 10, 0">
<StackPanel Orientation="Vertical" Spacing="20">
<StackPanel Orientation="Horizontal" Spacing="5">
<TextBlock VerticalAlignment="Center">Tab position:</TextBlock>
<atom:OptionButtonGroup ButtonStyle="Outline" Name="PositionCardTabStripOptionGroup">
<atom:OptionButton IsChecked="True">Top</atom:OptionButton>
<atom:OptionButton>Bottom</atom:OptionButton>
<atom:OptionButton>Left</atom:OptionButton>
<atom:OptionButton>Right</atom:OptionButton>
</atom:OptionButtonGroup>
</StackPanel>
<DockPanel Height="300">
<atom:CardTabStrip Name="PositionCardTabStrip"
DockPanel.Dock="{Binding PositionCardTabStripPlacement}"
TabStripPlacement="{Binding PositionCardTabStripPlacement}"
IsShowAddTabButton="True">
<atom:TabStripItem Icon="{atom:IconProvider Kind=AppleOutlined}">Tab 1</atom:TabStripItem>
<atom:TabStripItem Icon="{atom:IconProvider Kind=AndroidOutlined}">Tab 2</atom:TabStripItem>
<atom:TabStripItem>Tab 3</atom:TabStripItem>
<atom:TabStripItem Icon="{atom:IconProvider Kind=GithubOutlined}" IsClosable="True">Tab 4</atom:TabStripItem>
</atom:CardTabStrip>
<Border HorizontalAlignment="Stretch" VerticalAlignment="Stretch"
Background="rgb(241, 243, 246)">
<TextBlock HorizontalAlignment="Center" VerticalAlignment="Center">Tab Content</TextBlock>
</Border>
</DockPanel>
</StackPanel>
</showcase:ShowCaseItem>
<showcase:ShowCaseItem
Title="Size"
Description="Large size tabs are usually used in page header, and small size could be used in Modal.">
<StackPanel Orientation="Vertical" Spacing="20">
<StackPanel Orientation="Horizontal" Spacing="5">
<TextBlock VerticalAlignment="Center">Tab position:</TextBlock>
<atom:OptionButtonGroup ButtonStyle="Outline" Name="SizeTypeTabStripOptionGroup">
<atom:OptionButton>Small</atom:OptionButton>
<atom:OptionButton IsChecked="True">Middle</atom:OptionButton>
<atom:OptionButton>Large</atom:OptionButton>
</atom:OptionButtonGroup>
</StackPanel>
<atom:TabStrip SizeType="{Binding SizeTypeTabStrip}">
<atom:TabStripItem Icon="{atom:IconProvider Kind=AppleOutlined}">Tab 1</atom:TabStripItem>
<atom:TabStripItem Icon="{atom:IconProvider Kind=AndroidOutlined}">Tab 2</atom:TabStripItem>
<atom:TabStripItem Icon="{atom:IconProvider Kind=WechatOutlined}">Tab 3</atom:TabStripItem>
<atom:TabStripItem Icon="{atom:IconProvider Kind=GithubOutlined}" IsClosable="True">Tab 4</atom:TabStripItem>
</atom:TabStrip>
<atom:CardTabStrip SizeType="{Binding SizeTypeTabStrip}">
<atom:TabStripItem Icon="{atom:IconProvider Kind=AppleOutlined}">Tab 1</atom:TabStripItem>
<atom:TabStripItem Icon="{atom:IconProvider Kind=AndroidOutlined}">Tab 2</atom:TabStripItem>
<atom:TabStripItem Icon="{atom:IconProvider Kind=WechatOutlined}">Tab 3</atom:TabStripItem>
<atom:TabStripItem Icon="{atom:IconProvider Kind=GithubOutlined}" IsClosable="True">Tab 4</atom:TabStripItem>
</atom:CardTabStrip>
</StackPanel>
</showcase:ShowCaseItem>
<showcase:ShowCaseItem
Title="Add and close tab"
Description="Hide default plus icon, and bind event for customized trigger." Margin="0, 0, 30, 0">
<StackPanel Orientation="Vertical" Spacing="20">
<atom:CardTabStrip IsShowAddTabButton="True" Name="AddTabDemoStrip">
<atom:TabStripItem Icon="{atom:IconProvider Kind=AppleOutlined}">Tab 1</atom:TabStripItem>
<atom:TabStripItem Icon="{atom:IconProvider Kind=AndroidOutlined}">Tab 2</atom:TabStripItem>
<atom:TabStripItem Icon="{atom:IconProvider Kind=WechatOutlined}">Tab 3</atom:TabStripItem>
<atom:TabStripItem Icon="{atom:IconProvider Kind=GithubOutlined}" IsClosable="True">Tab 4</atom:TabStripItem>
</atom:CardTabStrip>
</StackPanel>
</showcase:ShowCaseItem>
</showcase:ShowCasePanel>
</TabItem>
</TabControl>
</UserControl>

View File

@ -1,14 +1,187 @@
using AtomUI.Controls;
using Avalonia;
using Avalonia.Controls;
using Button = AtomUI.Controls.Button;
using ToggleSwitch = AtomUI.Controls.ToggleSwitch;
using Avalonia.Interactivity;
using TabItem = AtomUI.Controls.TabItem;
namespace AtomUI.Demo.Desktop.ShowCase;
public partial class TabControlShowCase : UserControl
{
#region TabStrip
public static readonly StyledProperty<Dock> PositionTabStripPlacementProperty =
AvaloniaProperty.Register<TabControlShowCase, Dock>(nameof(PositionTabStripPlacement), Dock.Top);
public static readonly StyledProperty<Dock> PositionCardTabStripPlacementProperty =
AvaloniaProperty.Register<TabControlShowCase, Dock>(nameof(PositionCardTabStripPlacement), Dock.Top);
public static readonly StyledProperty<SizeType> SizeTypeTabStripProperty =
AvaloniaProperty.Register<TabControlShowCase, SizeType>(nameof(SizeTypeTabStrip), SizeType.Middle);
public Dock PositionTabStripPlacement
{
get => GetValue(PositionTabStripPlacementProperty);
set => SetValue(PositionTabStripPlacementProperty, value);
}
public Dock PositionCardTabStripPlacement
{
get => GetValue(PositionCardTabStripPlacementProperty);
set => SetValue(PositionCardTabStripPlacementProperty, value);
}
public SizeType SizeTypeTabStrip
{
get => GetValue(SizeTypeTabStripProperty);
set => SetValue(SizeTypeTabStripProperty, value);
}
#endregion
#region TabControl
public static readonly StyledProperty<Dock> PositionTabControlPlacementProperty =
AvaloniaProperty.Register<TabControlShowCase, Dock>(nameof(PositionTabControlPlacement), Dock.Top);
public static readonly StyledProperty<Dock> PositionCardTabControlPlacementProperty =
AvaloniaProperty.Register<TabControlShowCase, Dock>(nameof(PositionCardTabControlPlacement), Dock.Top);
public static readonly StyledProperty<SizeType> SizeTypeTabControlProperty =
AvaloniaProperty.Register<TabControlShowCase, SizeType>(nameof(SizeTypeTabControl), SizeType.Middle);
public Dock PositionTabControlPlacement
{
get => GetValue(PositionTabControlPlacementProperty);
set => SetValue(PositionTabControlPlacementProperty, value);
}
public Dock PositionCardTabControlPlacement
{
get => GetValue(PositionCardTabControlPlacementProperty);
set => SetValue(PositionCardTabControlPlacementProperty, value);
}
public SizeType SizeTypeTabControl
{
get => GetValue(SizeTypeTabControlProperty);
set => SetValue(SizeTypeTabControlProperty, value);
}
#endregion
public TabControlShowCase()
{
InitializeComponent();
DataContext = this;
PositionTabStripOptionGroup.OptionCheckedChanged += HandleTabStripPlacementOptionCheckedChanged;
PositionCardTabStripOptionGroup.OptionCheckedChanged += HandleCardTabStripPlacementOptionCheckedChanged;
SizeTypeTabStripOptionGroup.OptionCheckedChanged += HandleTabStripSizeTypeOptionCheckedChanged;
AddTabDemoStrip.AddTabRequest += HandleTabStripAddTabRequest;
PositionTabControlOptionGroup.OptionCheckedChanged += HandleTabControlPlacementOptionCheckedChanged;
PositionCardTabControlOptionGroup.OptionCheckedChanged += HandleCardTabControlPlacementOptionCheckedChanged;
SizeTypeTabControlOptionGroup.OptionCheckedChanged += HandleTabControlSizeTypeOptionCheckedChanged;
AddTabDemoTabControl.AddTabRequest += HandleTabControlAddTabRequest;
}
#region TabStrip
private void HandleTabStripPlacementOptionCheckedChanged(object? sender, OptionCheckedChangedEventArgs args)
{
if (args.Index == 0) {
PositionTabStripPlacement = Dock.Top;
} else if (args.Index == 1) {
PositionTabStripPlacement = Dock.Bottom;
} else if (args.Index == 2) {
PositionTabStripPlacement = Dock.Left;
} else {
PositionTabStripPlacement = Dock.Right;
}
}
private void HandleCardTabStripPlacementOptionCheckedChanged(object? sender, OptionCheckedChangedEventArgs args)
{
if (args.Index == 0) {
PositionCardTabStripPlacement = Dock.Top;
} else if (args.Index == 1) {
PositionCardTabStripPlacement = Dock.Bottom;
} else if (args.Index == 2) {
PositionCardTabStripPlacement = Dock.Left;
} else {
PositionCardTabStripPlacement = Dock.Right;
}
}
private void HandleTabStripSizeTypeOptionCheckedChanged(object? sender, OptionCheckedChangedEventArgs args)
{
if (args.Index == 0) {
SizeTypeTabStrip = SizeType.Small;
} else if (args.Index == 1) {
SizeTypeTabStrip = SizeType.Middle;
} else {
SizeTypeTabStrip = SizeType.Large;
}
}
private void HandleTabStripAddTabRequest(object? sender, RoutedEventArgs args)
{
var index = AddTabDemoStrip.ItemCount;
AddTabDemoStrip.Items.Add(new TabStripItem()
{
Content = $"new tab {index}",
IsClosable = true
});
}
#endregion
#region TabControl
private void HandleTabControlPlacementOptionCheckedChanged(object? sender, OptionCheckedChangedEventArgs args)
{
if (args.Index == 0) {
PositionTabControlPlacement = Dock.Top;
} else if (args.Index == 1) {
PositionTabControlPlacement = Dock.Bottom;
} else if (args.Index == 2) {
PositionTabControlPlacement = Dock.Left;
} else {
PositionTabControlPlacement = Dock.Right;
}
}
private void HandleCardTabControlPlacementOptionCheckedChanged(object? sender, OptionCheckedChangedEventArgs args)
{
if (args.Index == 0) {
PositionCardTabControlPlacement = Dock.Top;
} else if (args.Index == 1) {
PositionCardTabControlPlacement = Dock.Bottom;
} else if (args.Index == 2) {
PositionCardTabControlPlacement = Dock.Left;
} else {
PositionCardTabControlPlacement = Dock.Right;
}
}
private void HandleTabControlSizeTypeOptionCheckedChanged(object? sender, OptionCheckedChangedEventArgs args)
{
if (args.Index == 0) {
SizeTypeTabControl = SizeType.Small;
} else if (args.Index == 1) {
SizeTypeTabControl = SizeType.Middle;
} else {
SizeTypeTabControl = SizeType.Large;
}
}
private void HandleTabControlAddTabRequest(object? sender, RoutedEventArgs args)
{
var index = AddTabDemoTabControl.ItemCount;
AddTabDemoTabControl.Items.Add(new TabItem()
{
Header = $"new tab {index}",
Content = $"new tab content {index}",
IsClosable = true
});
}
#endregion
}

View File

@ -11,7 +11,7 @@ namespace AtomUI.Controls;
[ControlThemeProvider]
public class ArrowDecoratedBoxTheme : BaseControlTheme
{
public const string DecoratorPart = "Part_Decorator";
public const string DecoratorPart = "PART_Decorator";
public ArrowDecoratedBoxTheme() : base(typeof(ArrowDecoratedBox)) {}

View File

@ -2,6 +2,7 @@ using AtomUI.Icon;
using AtomUI.Theme.Styling;
using Avalonia;
using Avalonia.Input;
using Avalonia.Layout;
using Avalonia.LogicalTree;
using Avalonia.Rendering;
@ -39,6 +40,8 @@ public class IconButton : AvaloniaButton, ICustomHitTest
base.OnAttachedToLogicalTree(e);
if (!_initialized) {
if (Icon is not null) {
Icon.VerticalAlignment = VerticalAlignment.Center;
Icon.HorizontalAlignment = HorizontalAlignment.Center;
Content = Icon;
}
_initialized = true;

View File

@ -294,7 +294,7 @@ namespace AtomUI.Theme.Styling
public static class TabControlResourceKey
{
public static readonly TokenResourceKey CardBg = new TokenResourceKey("TabControl.CardBg");
public static readonly TokenResourceKey CardHeight = new TokenResourceKey("TabControl.CardHeight");
public static readonly TokenResourceKey CardSize = new TokenResourceKey("TabControl.CardSize");
public static readonly TokenResourceKey CardPadding = new TokenResourceKey("TabControl.CardPadding");
public static readonly TokenResourceKey CardPaddingSM = new TokenResourceKey("TabControl.CardPaddingSM");
public static readonly TokenResourceKey CardPaddingLG = new TokenResourceKey("TabControl.CardPaddingLG");
@ -308,8 +308,8 @@ namespace AtomUI.Theme.Styling
public static readonly TokenResourceKey HorizontalItemPadding = new TokenResourceKey("TabControl.HorizontalItemPadding");
public static readonly TokenResourceKey HorizontalItemPaddingLG = new TokenResourceKey("TabControl.HorizontalItemPaddingLG");
public static readonly TokenResourceKey HorizontalItemPaddingSM = new TokenResourceKey("TabControl.HorizontalItemPaddingSM");
public static readonly TokenResourceKey VerticalItemGutter = new TokenResourceKey("TabControl.VerticalItemGutter");
public static readonly TokenResourceKey VerticalItemPadding = new TokenResourceKey("TabControl.VerticalItemPadding");
public static readonly TokenResourceKey VerticalItemMargin = new TokenResourceKey("TabControl.VerticalItemMargin");
public static readonly TokenResourceKey ItemColor = new TokenResourceKey("TabControl.ItemColor");
public static readonly TokenResourceKey ItemHoverColor = new TokenResourceKey("TabControl.ItemHoverColor");
public static readonly TokenResourceKey ItemSelectedColor = new TokenResourceKey("TabControl.ItemSelectedColor");
@ -318,6 +318,10 @@ namespace AtomUI.Theme.Styling
public static readonly TokenResourceKey MenuIndicatorPaddingHorizontal = new TokenResourceKey("TabControl.MenuIndicatorPaddingHorizontal");
public static readonly TokenResourceKey MenuIndicatorPaddingVertical = new TokenResourceKey("TabControl.MenuIndicatorPaddingVertical");
public static readonly TokenResourceKey MenuEdgeThickness = new TokenResourceKey("TabControl.MenuEdgeThickness");
public static readonly TokenResourceKey AddTabButtonMarginHorizontal = new TokenResourceKey("TabControl.AddTabButtonMarginHorizontal");
public static readonly TokenResourceKey AddTabButtonMarginVertical = new TokenResourceKey("TabControl.AddTabButtonMarginVertical");
public static readonly TokenResourceKey CloseIconMargin = new TokenResourceKey("TabControl.CloseIconMargin");
public static readonly TokenResourceKey TabAndContentGutter = new TokenResourceKey("TabControl.TabAndContentGutter");
}
public static class TagResourceKey

View File

@ -8,8 +8,8 @@ namespace AtomUI.Controls;
[ControlThemeProvider]
public class LoadingIndicatorAdornerTheme : BaseControlTheme
{
public const string LoadingIndicatorPart = "Part_LoadingIndicator";
public const string MainContainerPart = "Part_MainContainer";
public const string LoadingIndicatorPart = "PART_LoadingIndicator";
public const string MainContainerPart = "PART_MainContainer";
public LoadingIndicatorAdornerTheme()
: base(typeof(LoadingIndicatorAdorner))

View File

@ -12,8 +12,8 @@ namespace AtomUI.Controls;
[ControlThemeProvider]
public class LoadingIndicatorTheme : BaseControlTheme
{
public const string MainContainerPart = "Part_MainContainer";
public const string LoadingTextPart = "Part_LoadingText";
public const string MainContainerPart = "PART_MainContainer";
public const string LoadingTextPart = "PART_LoadingText";
public LoadingIndicatorTheme()
: base(typeof(LoadingIndicator))

View File

@ -17,13 +17,13 @@ namespace AtomUI.Controls;
[ControlThemeProvider]
internal class MenuItemTheme : BaseControlTheme
{
public const string ItemDecoratorPart = "Part_ItemDecorator";
public const string MainContainerPart = "Part_MainContainer";
public const string TogglePresenterPart = "Part_TogglePresenter";
public const string ItemIconPresenterPart = "Part_ItemIconPresenter";
public const string ItemTextPresenterPart = "Part_ItemTextPresenter";
public const string InputGestureTextPart = "Part_InputGestureText";
public const string MenuIndicatorIconPart = "Part_MenuIndicatorIcon";
public const string ItemDecoratorPart = "PART_ItemDecorator";
public const string MainContainerPart = "PART_MainContainer";
public const string TogglePresenterPart = "PART_TogglePresenter";
public const string ItemIconPresenterPart = "PART_ItemIconPresenter";
public const string ItemTextPresenterPart = "PART_ItemTextPresenter";
public const string InputGestureTextPart = "PART_InputGestureText";
public const string MenuIndicatorIconPart = "PART_MenuIndicatorIcon";
public const string PopupPart = "PART_Popup";
public const string ItemsPresenterPart = "PART_ItemsPresenter";

View File

@ -17,10 +17,10 @@ namespace AtomUI.Controls;
[ControlThemeProvider]
internal class MenuScrollViewerTheme : BaseControlTheme
{
public const string ScrollUpButtonPart = "Part_ScrollUpButton";
public const string ScrollDownButtonPart = "Part_ScrollDownButton";
public const string ScrollUpButtonPart = "PART_ScrollUpButton";
public const string ScrollDownButtonPart = "PART_ScrollDownButton";
public const string ScrollViewContentPart = "PART_ContentPresenter";
public const string MainContainerPart = "Part_MainContainer";
public const string MainContainerPart = "PART_MainContainer";
public MenuScrollViewerTheme()
: base(typeof(MenuScrollViewer)) { }

View File

@ -15,9 +15,20 @@ namespace AtomUI.Controls;
using ButtonSizeType = SizeType;
using OptionButtons = AvaloniaList<OptionButton>;
public class OptionButtonGroup : TemplatedControl,
ISizeTypeAware,
IControlCustomStyle
public class OptionCheckedChangedEventArgs : RoutedEventArgs
{
public OptionCheckedChangedEventArgs(RoutedEvent routedEvent, OptionButton option, int index)
: base(routedEvent)
{
CheckedOption = option;
Index = index;
}
public OptionButton CheckedOption { get; }
public int Index { get; }
}
public class OptionButtonGroup : TemplatedControl, ISizeTypeAware, IControlCustomStyle
{
public static readonly StyledProperty<ButtonSizeType> SizeTypeProperty =
AvaloniaProperty.Register<OptionButtonGroup, ButtonSizeType>(nameof(SizeType), ButtonSizeType.Middle);
@ -32,6 +43,17 @@ public class OptionButtonGroup : TemplatedControl,
internal static readonly StyledProperty<IBrush?> SelectedOptionBorderColorProperty =
AvaloniaProperty.Register<Button, IBrush?>(nameof(SelectedOptionBorderColor));
public static readonly RoutedEvent<OptionCheckedChangedEventArgs> OptionCheckedChangedEvent =
RoutedEvent.Register<SelectingItemsControl, OptionCheckedChangedEventArgs>(
nameof(OptionCheckedChanged),
RoutingStrategies.Bubble);
public event EventHandler<OptionCheckedChangedEventArgs>? OptionCheckedChanged
{
add => AddHandler(OptionCheckedChangedEvent, value);
remove => RemoveHandler(OptionCheckedChangedEvent, value);
}
public ButtonSizeType SizeType
{
@ -155,6 +177,7 @@ public class OptionButtonGroup : TemplatedControl,
if (sender is OptionButton optionButton) {
if (optionButton.IsChecked.HasValue && optionButton.IsChecked.Value) {
SelectedOption = optionButton;
RaiseEvent(new OptionCheckedChangedEventArgs(OptionCheckedChangedEvent, optionButton, Options.IndexOf(optionButton)));
}
}
}

View File

@ -0,0 +1,63 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.Primitives;
using Avalonia.Interactivity;
using Avalonia.Threading;
namespace AtomUI.Controls;
internal class BaseOverflowMenuItem : MenuItem
{
#region
public static readonly DirectProperty<BaseOverflowMenuItem, bool> IsClosableProperty =
AvaloniaProperty.RegisterDirect<BaseOverflowMenuItem, bool>(nameof(IsClosable),
o => o.IsClosable,
(o, v) => o.IsClosable = v);
public static readonly RoutedEvent<CloseTabRequestEventArgs> CloseTabEvent =
RoutedEvent.Register<Button, CloseTabRequestEventArgs>(nameof(CloseTab), RoutingStrategies.Bubble);
private bool _isClosable = false;
public bool IsClosable
{
get => _isClosable;
set => SetAndRaise(IsClosableProperty, ref _isClosable, value);
}
public event EventHandler<CloseTabRequestEventArgs>? CloseTab
{
add => AddHandler(CloseTabEvent, value);
remove => RemoveHandler(CloseTabEvent, value);
}
#endregion
private IconButton? _iconButton;
protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
{
base.OnApplyTemplate(e);
_iconButton = e.NameScope.Find<IconButton>(BaseOverflowMenuItemTheme.ItemCloseButtonPart);
if (_iconButton is not null) {
_iconButton.Click += (sender, args) =>
{
NotifyCloseRequest();
};
}
}
protected virtual void NotifyCloseRequest()
{
}
}
internal class CloseTabRequestEventArgs : RoutedEventArgs
{
public CloseTabRequestEventArgs(RoutedEvent routedEvent, object tabItem)
: base(routedEvent)
{
TabItem = tabItem;
}
public object TabItem { get; }
}

View File

@ -13,21 +13,21 @@ using Avalonia.Styling;
namespace AtomUI.Controls;
[ControlThemeProvider]
internal class TabStripMenuItemTheme : BaseControlTheme
internal class BaseOverflowMenuItemTheme : BaseControlTheme
{
public const string ItemDecoratorPart = "Part_ItemDecorator";
public const string MainContainerPart = "Part_MainContainer";
public const string ItemTextPresenterPart = "Part_ItemTextPresenter";
public const string ItemCloseButtonPart = "Part_ItemCloseIcon";
public const string ItemDecoratorPart = "PART_ItemDecorator";
public const string MainContainerPart = "PART_MainContainer";
public const string ItemTextPresenterPart = "PART_ItemTextPresenter";
public const string ItemCloseButtonPart = "PART_ItemCloseIcon";
public TabStripMenuItemTheme()
: base(typeof(TabStripMenuItem))
public BaseOverflowMenuItemTheme()
: base(typeof(BaseOverflowMenuItem))
{
}
protected override IControlTemplate BuildControlTemplate()
{
return new FuncControlTemplate<TabStripMenuItem>((item, scope) =>
return new FuncControlTemplate<BaseOverflowMenuItem>((item, scope) =>
{
var container = new Border()
{
@ -61,8 +61,8 @@ internal class TabStripMenuItemTheme : BaseControlTheme
Grid.SetColumn(itemTextPresenter, 0);
TokenResourceBinder.CreateTokenBinding(itemTextPresenter, ContentPresenter.MarginProperty, MenuResourceKey.ItemMargin);
CreateTemplateParentBinding(itemTextPresenter, ContentPresenter.ContentProperty, TabStripMenuItem.HeaderProperty);
CreateTemplateParentBinding(itemTextPresenter, ContentPresenter.ContentTemplateProperty, TabStripMenuItem.HeaderTemplateProperty);
CreateTemplateParentBinding(itemTextPresenter, ContentPresenter.ContentProperty, BaseOverflowMenuItem.HeaderProperty);
CreateTemplateParentBinding(itemTextPresenter, ContentPresenter.ContentTemplateProperty, BaseOverflowMenuItem.HeaderTemplateProperty);
itemTextPresenter.RegisterInNameScope(scope);
@ -82,7 +82,7 @@ internal class TabStripMenuItemTheme : BaseControlTheme
};
CreateTemplateParentBinding(closeButton, IconButton.IsVisibleProperty, TabStripMenuItem.IsClosableProperty);
CreateTemplateParentBinding(closeButton, IconButton.IsVisibleProperty, BaseOverflowMenuItem.IsClosableProperty);
TokenResourceBinder.CreateGlobalTokenBinding(menuCloseIcon, PathIcon.NormalFilledBrushProperty, GlobalResourceKey.ColorIcon);
TokenResourceBinder.CreateGlobalTokenBinding(menuCloseIcon, PathIcon.ActiveFilledBrushProperty, GlobalResourceKey.ColorIconHover);
@ -110,7 +110,7 @@ internal class TabStripMenuItemTheme : BaseControlTheme
private void BuildCommonStyle(Style commonStyle)
{
commonStyle.Add(TabStripMenuItem.ForegroundProperty, MenuResourceKey.ItemColor);
commonStyle.Add(BaseOverflowMenuItem.ForegroundProperty, MenuResourceKey.ItemColor);
{
var borderStyle = new Style(selector => selector.Nesting().Template().Name(ItemDecoratorPart));
borderStyle.Add(Border.MinHeightProperty, MenuResourceKey.ItemHeight);
@ -122,7 +122,7 @@ internal class TabStripMenuItemTheme : BaseControlTheme
// Hover 状态
var hoverStyle = new Style(selector => selector.Nesting().Class(StdPseudoClass.PointerOver));
hoverStyle.Add(TabStripMenuItem.ForegroundProperty, MenuResourceKey.ItemHoverColor);
hoverStyle.Add(BaseOverflowMenuItem.ForegroundProperty, MenuResourceKey.ItemHoverColor);
{
var borderStyle = new Style(selector => selector.Nesting().Template().Name(ItemDecoratorPart));
borderStyle.Add(Border.BackgroundProperty, MenuResourceKey.ItemHoverBg);
@ -134,7 +134,7 @@ internal class TabStripMenuItemTheme : BaseControlTheme
private void BuildDisabledStyle()
{
var disabledStyle = new Style(selector => selector.Nesting().Class(StdPseudoClass.Disabled));
disabledStyle.Add(TabStripMenuItem.ForegroundProperty, MenuResourceKey.ItemDisabledColor);
disabledStyle.Add(BaseOverflowMenuItem.ForegroundProperty, MenuResourceKey.ItemDisabledColor);
Add(disabledStyle);
}

View File

@ -1,6 +1,185 @@
namespace AtomUI.Controls;
using AtomUI.Data;
using AtomUI.Theme.Data;
using AtomUI.Theme.Styling;
using AtomUI.Utils;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.Primitives;
using Avalonia.Controls.Templates;
using Avalonia.Data;
using Avalonia.LogicalTree;
using Avalonia.Media;
public class BaseTabControl
namespace AtomUI.Controls;
using AvaloniaTabControl = Avalonia.Controls.TabControl;
public class BaseTabControl : AvaloniaTabControl
{
public const string TopPC = ":top";
public const string RightPC = ":right";
public const string BottomPC = ":bottom";
public const string LeftPC = ":left";
private static readonly FuncTemplate<Panel?> DefaultPanel =
new(() => new StackPanel());
#region
public static readonly StyledProperty<SizeType> SizeTypeProperty =
AvaloniaProperty.Register<BaseTabControl, SizeType>(nameof(SizeType), SizeType.Middle);
public static readonly StyledProperty<bool> TabAlignmentCenterProperty =
AvaloniaProperty.Register<BaseTabControl, bool>(nameof(TabAlignmentCenter), false);
public SizeType SizeType
{
get => GetValue(SizeTypeProperty);
set => SetValue(SizeTypeProperty, value);
}
public bool TabAlignmentCenter
{
get => GetValue(TabAlignmentCenterProperty);
set => SetValue(TabAlignmentCenterProperty, value);
}
#endregion
#region
internal static readonly DirectProperty<BaseTabControl, double> TabAndContentGutterProperty =
AvaloniaProperty.RegisterDirect<BaseTabControl, double>(nameof(TabAndContentGutter),
o => o.TabAndContentGutter,
(o, v) => o.TabAndContentGutter = v);
private double _tabAndContentGutter;
internal double TabAndContentGutter
{
get => _tabAndContentGutter;
set => SetAndRaise(TabAndContentGutterProperty, ref _tabAndContentGutter, value);
}
internal static readonly DirectProperty<BaseTabControl, Thickness> TabStripMarginProperty =
AvaloniaProperty.RegisterDirect<BaseTabControl, Thickness>(nameof(TabStripMargin),
o => o.TabStripMargin,
(o, v) => o.TabStripMargin = v);
private Thickness _tabStripMargin;
internal Thickness TabStripMargin
{
get => _tabStripMargin;
set => SetAndRaise(TabStripMarginProperty, ref _tabStripMargin, value);
}
#endregion
private Border? _frameDecorator;
private Panel? _alignWrapper;
private Point _tabStripBorderStartPoint;
private Point _tabStripBorderEndPoint;
static BaseTabControl()
{
ItemsPanelProperty.OverrideDefaultValue<BaseTabControl>(DefaultPanel);
}
protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
{
base.OnApplyTemplate(e);
_frameDecorator = e.NameScope.Find<Border>(BaseTabControlTheme.FrameDecoratorPart);
_alignWrapper = e.NameScope.Find<Panel>(BaseTabControlTheme.AlignWrapperPart);
SetupBorderBinding();
HandlePlacementChanged();
}
private void SetupBorderBinding()
{
if (_frameDecorator is not null) {
TokenResourceBinder.CreateTokenBinding(this, BorderThicknessProperty, GlobalResourceKey.BorderThickness, BindingPriority.Template,
new RenderScaleAwareThicknessConfigure(this));
}
TokenResourceBinder.CreateTokenBinding(this, TabAndContentGutterProperty, TabControlResourceKey.TabAndContentGutter);
}
protected override void PrepareContainerForItemOverride(Control container, object? item, int index)
{
base.PrepareContainerForItemOverride(container, item, index);
if (container is TabItem tabItem) {
BindUtils.RelayBind(this, SizeTypeProperty, tabItem, TabItem.SizeTypeProperty);
}
}
protected override void OnAttachedToLogicalTree(LogicalTreeAttachmentEventArgs e)
{
base.OnAttachedToLogicalTree(e);
UpdatePseudoClasses();
}
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
{
base.OnPropertyChanged(change);
if (change.Property == TabStripPlacementProperty) {
HandlePlacementChanged();
}
}
private void UpdatePseudoClasses()
{
PseudoClasses.Set(TopPC, TabStripPlacement == Dock.Top);
PseudoClasses.Set(RightPC, TabStripPlacement == Dock.Right);
PseudoClasses.Set(BottomPC, TabStripPlacement == Dock.Bottom);
PseudoClasses.Set(LeftPC, TabStripPlacement == Dock.Left);
}
private void HandlePlacementChanged()
{
UpdatePseudoClasses();
if (TabStripPlacement == Dock.Top) {
TabStripMargin = new Thickness(0, 0, 0, _tabAndContentGutter);
} else if (TabStripPlacement == Dock.Right) {
TabStripMargin = new Thickness(_tabAndContentGutter, 0, 0, 0);
} else if (TabStripPlacement == Dock.Bottom) {
TabStripMargin = new Thickness(0, _tabAndContentGutter, 0, 0);
} else {
TabStripMargin = new Thickness(0, 0, _tabAndContentGutter, 0);
}
}
private void SetupTabStripBorderPoints()
{
if (_alignWrapper is not null) {
var offset = _alignWrapper.TranslatePoint(new Point(0, 0), this) ?? default;
var size = _alignWrapper.Bounds.Size;
var borderThickness = BorderThickness.Left;
var offsetDelta = borderThickness / 2;
if (TabStripPlacement == Dock.Top) {
_tabStripBorderStartPoint = new Point(0, size.Height - offsetDelta);
_tabStripBorderEndPoint = new Point(size.Width, size.Height - offsetDelta);
} else if (TabStripPlacement == Dock.Right) {
_tabStripBorderStartPoint = new Point(offsetDelta, 0);
_tabStripBorderEndPoint = new Point(offsetDelta, size.Height);
} else if (TabStripPlacement == Dock.Bottom) {
_tabStripBorderStartPoint = new Point(0, offsetDelta);
_tabStripBorderEndPoint = new Point(size.Width, offsetDelta);
} else {
_tabStripBorderStartPoint = new Point(size.Width - offsetDelta, 0);
_tabStripBorderEndPoint = new Point(size.Width - offsetDelta, size.Height);
}
_tabStripBorderStartPoint += offset;
_tabStripBorderEndPoint += offset;
}
}
public override void Render(DrawingContext context)
{
SetupTabStripBorderPoints();
var borderThickness = BorderThickness.Left;
using var optionState = context.PushRenderOptions(new RenderOptions()
{
EdgeMode = EdgeMode.Aliased
});
context.DrawLine(new Pen(BorderBrush, borderThickness), _tabStripBorderStartPoint, _tabStripBorderEndPoint);
}
}

View File

@ -0,0 +1,145 @@
using AtomUI.Theme;
using AtomUI.Theme.Styling;
using Avalonia.Controls;
using Avalonia.Controls.Presenters;
using Avalonia.Controls.Templates;
using Avalonia.Layout;
using Avalonia.Styling;
namespace AtomUI.Controls;
internal class BaseTabControlTheme : BaseControlTheme
{
public const string FrameDecoratorPart = "PART_FrameDecorator";
public const string ItemsPresenterPart = "PART_ItemsPresenter";
public const string MainLayoutContainerPart = "PART_MainLayoutContainer";
public const string SelectedContentHostPart = "PART_SelectedContentHost";
public const string TabsContainerPart = "PART_TabsContainer";
public const string AlignWrapperPart = "PART_AlignWrapper";
public BaseTabControlTheme(Type targetType) : base(targetType) { }
protected override IControlTemplate BuildControlTemplate()
{
return new FuncControlTemplate<BaseTabControl>((baseTabControl, scope) =>
{
var frameDecorator = new Border()
{
Name = FrameDecoratorPart
};
frameDecorator.RegisterInNameScope(scope);
var layoutContainer = new DockPanel()
{
Name = MainLayoutContainerPart
};
NotifyBuildTabStripTemplate(baseTabControl, scope, layoutContainer);
NotifyBuildContentPresenter(baseTabControl, scope, layoutContainer);
frameDecorator.Child = layoutContainer;
return frameDecorator;
});
}
protected virtual void NotifyBuildTabStripTemplate(BaseTabControl baseTabControl, INameScope scope, DockPanel container)
{
}
protected virtual void NotifyBuildContentPresenter(BaseTabControl baseTabControl, INameScope scope, DockPanel container)
{
var contentPresenter = new ContentPresenter
{
Name = SelectedContentHostPart
};
contentPresenter.RegisterInNameScope(scope);
CreateTemplateParentBinding(contentPresenter, ContentPresenter.MarginProperty, BaseTabControl.PaddingProperty);
CreateTemplateParentBinding(contentPresenter, ContentPresenter.ContentProperty, BaseTabControl.SelectedContentProperty);
CreateTemplateParentBinding(contentPresenter, ContentPresenter.ContentTemplateProperty, BaseTabControl.SelectedContentTemplateProperty);
CreateTemplateParentBinding(contentPresenter, ContentPresenter.HorizontalContentAlignmentProperty, BaseTabControl.HorizontalContentAlignmentProperty);
CreateTemplateParentBinding(contentPresenter, ContentPresenter.VerticalContentAlignmentProperty, BaseTabControl.VerticalContentAlignmentProperty);
container.Children.Add(contentPresenter);
}
protected override void BuildStyles()
{
base.BuildStyles();
var commonStyle = new Style(selector => selector.Nesting());
commonStyle.Add(BaseTabControl.BorderBrushProperty, GlobalResourceKey.ColorBorderSecondary);
// 设置 items presenter 是否居中
// 分为上、右、下、左
{
// 上
var topStyle = new Style(selector => selector.Nesting().Class(BaseTabControl.TopPC));
topStyle.Add(BaseTabControl.HorizontalAlignmentProperty, HorizontalAlignment.Stretch);
topStyle.Add(BaseTabControl.VerticalAlignmentProperty, VerticalAlignment.Top);
// tabs 是否居中
var tabAlignCenterStyle = new Style(selector => selector.Nesting().PropertyEquals(TabControl.TabAlignmentCenterProperty, true));
{
var tabsContainerStyle = new Style(selector => selector.Nesting().Template().Name(TabsContainerPart));
tabsContainerStyle.Add(ItemsPresenter.HorizontalAlignmentProperty, HorizontalAlignment.Center);
tabAlignCenterStyle.Add(tabsContainerStyle);
}
topStyle.Add(tabAlignCenterStyle);
commonStyle.Add(topStyle);
}
{
// 右
var rightStyle = new Style(selector => selector.Nesting().Class(BaseTabControl.RightPC));
rightStyle.Add(BaseTabControl.HorizontalAlignmentProperty, HorizontalAlignment.Left);
rightStyle.Add(BaseTabControl.VerticalAlignmentProperty, VerticalAlignment.Stretch);
// tabs 是否居中
var tabAlignCenterStyle = new Style(selector => selector.Nesting().PropertyEquals(TabControl.TabAlignmentCenterProperty, true));
{
var tabsContainerStyle = new Style(selector => selector.Nesting().Template().Name(TabsContainerPart));
tabsContainerStyle.Add(ItemsPresenter.VerticalAlignmentProperty, VerticalAlignment.Center);
tabAlignCenterStyle.Add(tabsContainerStyle);
}
rightStyle.Add(tabAlignCenterStyle);
commonStyle.Add(rightStyle);
}
{
// 下
var bottomStyle = new Style(selector => selector.Nesting().Class(BaseTabControl.BottomPC));
bottomStyle.Add(BaseTabControl.HorizontalAlignmentProperty, HorizontalAlignment.Stretch);
bottomStyle.Add(BaseTabControl.VerticalAlignmentProperty, VerticalAlignment.Top);
// tabs 是否居中
var tabAlignCenterStyle = new Style(selector => selector.Nesting().PropertyEquals(TabControl.TabAlignmentCenterProperty, true));
{
var tabsContainerStyle = new Style(selector => selector.Nesting().Template().Name(TabsContainerPart));
tabsContainerStyle.Add(ItemsPresenter.HorizontalAlignmentProperty, HorizontalAlignment.Center);
tabAlignCenterStyle.Add(tabsContainerStyle);
}
bottomStyle.Add(tabAlignCenterStyle);
commonStyle.Add(bottomStyle);
}
{
// 左
var leftStyle = new Style(selector => selector.Nesting().Class(BaseTabControl.LeftPC));
leftStyle.Add(BaseTabControl.HorizontalAlignmentProperty, HorizontalAlignment.Left);
leftStyle.Add(BaseTabControl.VerticalAlignmentProperty, VerticalAlignment.Stretch);
// tabs 是否居中
var tabAlignCenterStyle = new Style(selector => selector.Nesting().PropertyEquals(TabControl.TabAlignmentCenterProperty, true));
{
var tabsContainerStyle = new Style(selector => selector.Nesting().Template().Name(TabsContainerPart));
tabsContainerStyle.Add(ItemsPresenter.VerticalAlignmentProperty, VerticalAlignment.Center);
tabAlignCenterStyle.Add(tabsContainerStyle);
}
leftStyle.Add(tabAlignCenterStyle);
commonStyle.Add(leftStyle);
}
Add(commonStyle);
}
}

View File

@ -0,0 +1,197 @@
using AtomUI.Icon;
using AtomUI.Theme;
using AtomUI.Theme.Styling;
using AtomUI.Utils;
using Avalonia.Controls;
using Avalonia.Controls.Presenters;
using Avalonia.Controls.Templates;
using Avalonia.Input;
using Avalonia.Layout;
using Avalonia.Styling;
namespace AtomUI.Controls;
internal class BaseTabItemTheme : BaseControlTheme
{
public const string DecoratorPart = "PART_Decorator";
public const string ContentLayoutPart = "PART_ContentLayout";
public const string ContentPresenterPart = "PART_ContentPresenter";
public const string ItemIconPart = "PART_ItemIcon";
public const string ItemCloseButtonPart = "PART_ItemCloseButton";
public BaseTabItemTheme(Type targetType) : base(targetType) { }
protected override IControlTemplate BuildControlTemplate()
{
return new FuncControlTemplate<TabItem>((tabItem, scope) =>
{
// 做边框
var decorator = new Border()
{
Name = DecoratorPart
};
decorator.RegisterInNameScope(scope);
NotifyBuildControlTemplate(tabItem, scope, decorator);
return decorator;
});
}
protected virtual void NotifyBuildControlTemplate(TabItem tabItem, INameScope scope, Border container)
{
var containerLayout = new StackPanel()
{
Name = ContentLayoutPart,
Orientation = Orientation.Horizontal
};
containerLayout.RegisterInNameScope(scope);
var contentPresenter = new ContentPresenter()
{
Name = ContentPresenterPart
};
containerLayout.Children.Add(contentPresenter);
CreateTemplateParentBinding(contentPresenter, ContentPresenter.ContentProperty, TabItem.HeaderProperty);
CreateTemplateParentBinding(contentPresenter, ContentPresenter.ContentTemplateProperty, TabItem.HeaderTemplateProperty);
var iconButton = new IconButton()
{
Name = ItemCloseButtonPart
};
iconButton.RegisterInNameScope(scope);
TokenResourceBinder.CreateTokenBinding(iconButton, IconButton.MarginProperty, TabControlResourceKey.CloseIconMargin);
CreateTemplateParentBinding(iconButton, IconButton.IconProperty, TabItem.CloseIconProperty);
CreateTemplateParentBinding(iconButton, IconButton.IsVisibleProperty, TabItem.IsClosableProperty);
containerLayout.Children.Add(iconButton);
container.Child = containerLayout;
}
protected override void BuildStyles()
{
var commonStyle = new Style(selector => selector.Nesting());
commonStyle.Add(TabItem.CursorProperty, new Cursor(StandardCursorType.Hand));
commonStyle.Add(TabItem.ForegroundProperty, TabControlResourceKey.ItemColor);
// Icon 一些通用属性
{
var iconStyle = new Style(selector => selector.Nesting().Template().Name(ItemIconPart));
iconStyle.Add(PathIcon.MarginProperty, TabControlResourceKey.ItemIconMargin);
commonStyle.Add(iconStyle);
}
// hover
var hoverStyle = new Style(selector => selector.Nesting().Class(StdPseudoClass.PointerOver));
hoverStyle.Add(TabItem.ForegroundProperty, TabControlResourceKey.ItemHoverColor);
{
var iconStyle = new Style(selector => selector.Nesting().Template().Name(ItemIconPart));
iconStyle.Add(PathIcon.IconModeProperty, IconMode.Active);
hoverStyle.Add(iconStyle);
}
commonStyle.Add(hoverStyle);
// 选中
var selectedStyle = new Style(selector => selector.Nesting().Class(StdPseudoClass.Selected));
selectedStyle.Add(TabItem.ForegroundProperty, TabControlResourceKey.ItemSelectedColor);
{
var iconStyle = new Style(selector => selector.Nesting().Template().Name(ItemIconPart));
iconStyle.Add(PathIcon.IconModeProperty, IconMode.Selected);
selectedStyle.Add(iconStyle);
}
commonStyle.Add(selectedStyle);
Add(commonStyle);
BuildSizeTypeStyle();
BuildPlacementStyle();
BuildDisabledStyle();
}
private void BuildSizeTypeStyle()
{
var largeSizeStyle = new Style(selector => selector.Nesting().PropertyEquals(TabItem.SizeTypeProperty, SizeType.Large));
largeSizeStyle.Add(TabItem.FontSizeProperty, TabControlResourceKey.TitleFontSizeLG);
{
// Icon
var iconStyle = new Style(selector => selector.Nesting().Template().Name(ItemIconPart));
iconStyle.Add(PathIcon.WidthProperty, GlobalResourceKey.IconSize);
iconStyle.Add(PathIcon.HeightProperty, GlobalResourceKey.IconSize);
largeSizeStyle.Add(iconStyle);
}
Add(largeSizeStyle);
var middleSizeStyle = new Style(selector => selector.Nesting().PropertyEquals(TabItem.SizeTypeProperty, SizeType.Middle));
{
// Icon
var iconStyle = new Style(selector => selector.Nesting().Template().Name(ItemIconPart));
iconStyle.Add(PathIcon.WidthProperty, GlobalResourceKey.IconSize);
iconStyle.Add(PathIcon.HeightProperty, GlobalResourceKey.IconSize);
middleSizeStyle.Add(iconStyle);
}
middleSizeStyle.Add(TabItem.FontSizeProperty, TabControlResourceKey.TitleFontSize);
Add(middleSizeStyle);
var smallSizeType = new Style(selector => selector.Nesting().PropertyEquals(TabItem.SizeTypeProperty, SizeType.Small));
{
// Icon
var iconStyle = new Style(selector => selector.Nesting().Template().Name(ItemIconPart));
iconStyle.Add(PathIcon.WidthProperty, GlobalResourceKey.IconSizeSM);
iconStyle.Add(PathIcon.HeightProperty, GlobalResourceKey.IconSizeSM);
smallSizeType.Add(iconStyle);
}
smallSizeType.Add(TabItem.FontSizeProperty, TabControlResourceKey.TitleFontSizeSM);
Add(smallSizeType);
}
private void BuildPlacementStyle()
{
// 设置 items presenter 面板样式
// 分为上、右、下、左
{
// 上
var topStyle = new Style(selector => selector.Nesting().PropertyEquals(TabItem.TabStripPlacementProperty, Dock.Top));
var iconStyle = new Style(selector => selector.Nesting().Template().OfType<PathIcon>());
iconStyle.Add(PathIcon.VerticalAlignmentProperty, VerticalAlignment.Center);
topStyle.Add(iconStyle);
Add(topStyle);
}
{
// 右
var rightStyle = new Style(selector => selector.Nesting().PropertyEquals(TabItem.TabStripPlacementProperty, Dock.Right));
var iconStyle = new Style(selector => selector.Nesting().Template().OfType<PathIcon>());
iconStyle.Add(PathIcon.HorizontalAlignmentProperty, HorizontalAlignment.Left);
iconStyle.Add(PathIcon.VerticalAlignmentProperty, VerticalAlignment.Center);
rightStyle.Add(iconStyle);
Add(rightStyle);
}
{
// 下
var bottomStyle = new Style(selector => selector.Nesting().PropertyEquals(TabItem.TabStripPlacementProperty, Dock.Bottom));
var iconStyle = new Style(selector => selector.Nesting().Template().OfType<PathIcon>());
iconStyle.Add(PathIcon.VerticalAlignmentProperty, VerticalAlignment.Center);
bottomStyle.Add(iconStyle);
Add(bottomStyle);
}
{
// 左
var leftStyle = new Style(selector => selector.Nesting().PropertyEquals(TabItem.TabStripPlacementProperty, Dock.Left));
var iconStyle = new Style(selector => selector.Nesting().Template().OfType<PathIcon>());
iconStyle.Add(PathIcon.VerticalAlignmentProperty, VerticalAlignment.Center);
iconStyle.Add(PathIcon.HorizontalAlignmentProperty, HorizontalAlignment.Left);
leftStyle.Add(iconStyle);
Add(leftStyle);
}
}
private void BuildDisabledStyle()
{
var disabledStyle = new Style(selector => selector.Nesting().Class(StdPseudoClass.Disabled));
disabledStyle.Add(TabItem.ForegroundProperty, GlobalResourceKey.ColorTextDisabled);
Add(disabledStyle);
}
}

View File

@ -7,36 +7,34 @@ using Avalonia.Controls.Converters;
using Avalonia.Controls.Metadata;
using Avalonia.Controls.Presenters;
using Avalonia.Controls.Primitives;
using Avalonia.Interactivity;
using Avalonia.Media;
using Avalonia.Threading;
using Colors = Avalonia.Media.Colors;
using GradientStop = Avalonia.Media.GradientStop;
namespace AtomUI.Controls;
[TemplatePart(TabScrollViewerTheme.ScrollStartEdgeIndicatorPart, typeof(Control))]
[TemplatePart(TabScrollViewerTheme.ScrollEndEdgeIndicatorPart, typeof(Control))]
[TemplatePart(TabScrollViewerTheme.ScrollMenuIndicatorPart, typeof(IconButton))]
[TemplatePart(TabScrollViewerTheme.ScrollViewContentPart, typeof(ScrollContentPresenter))]
public class TabScrollViewer : ScrollViewer
[TemplatePart(BaseTabScrollViewerTheme.ScrollStartEdgeIndicatorPart, typeof(Control))]
[TemplatePart(BaseTabScrollViewerTheme.ScrollEndEdgeIndicatorPart, typeof(Control))]
[TemplatePart(BaseTabScrollViewerTheme.ScrollMenuIndicatorPart, typeof(IconButton))]
[TemplatePart(BaseTabScrollViewerTheme.ScrollViewContentPart, typeof(ScrollContentPresenter))]
internal abstract class BaseTabScrollViewer : ScrollViewer
{
private const int EdgeIndicatorZIndex = 1000;
#region
internal static readonly DirectProperty<TabScrollViewer, Dock> TabStripPlacementProperty =
AvaloniaProperty.RegisterDirect<TabScrollViewer, Dock>(nameof(TabStripPlacement),
internal static readonly DirectProperty<BaseTabScrollViewer, Dock> TabStripPlacementProperty =
AvaloniaProperty.RegisterDirect<BaseTabScrollViewer, Dock>(nameof(TabStripPlacement),
o => o.TabStripPlacement,
(o, v) => o.TabStripPlacement = v);
internal static readonly DirectProperty<TabScrollViewer, IBrush?> EdgeShadowStartColorProperty =
AvaloniaProperty.RegisterDirect<TabScrollViewer, IBrush?>(nameof(EdgeShadowStartColor),
internal static readonly DirectProperty<BaseTabScrollViewer, IBrush?> EdgeShadowStartColorProperty =
AvaloniaProperty.RegisterDirect<BaseTabScrollViewer, IBrush?>(nameof(EdgeShadowStartColor),
o => o.EdgeShadowStartColor,
(o, v) => o.EdgeShadowStartColor = v);
internal static readonly DirectProperty<TabScrollViewer, double> MenuEdgeThicknessProperty =
AvaloniaProperty.RegisterDirect<TabScrollViewer, double>(nameof(MenuEdgeThickness),
internal static readonly DirectProperty<BaseTabScrollViewer, double> MenuEdgeThicknessProperty =
AvaloniaProperty.RegisterDirect<BaseTabScrollViewer, double>(nameof(MenuEdgeThickness),
o => o.MenuEdgeThickness,
(o, v) => o.MenuEdgeThickness = v);
@ -64,21 +62,19 @@ public class TabScrollViewer : ScrollViewer
set => SetAndRaise(MenuEdgeThicknessProperty, ref _menuEdgeThickness, value);
}
internal BaseTabStrip? TabStrip { get; set; }
#endregion
private IconButton? _menuIndicator;
private Border? _startEdgeIndicator;
private Border? _endEdgeIndicator;
private MenuFlyout? _menuFlyout;
private protected IconButton? _menuIndicator;
private protected Border? _startEdgeIndicator;
private protected Border? _endEdgeIndicator;
private protected MenuFlyout? _menuFlyout;
static TabScrollViewer()
static BaseTabScrollViewer()
{
AffectsMeasure<TabScrollViewer>(TabStripPlacementProperty);
AffectsMeasure<BaseTabScrollViewer>(TabStripPlacementProperty);
}
public TabScrollViewer()
public BaseTabScrollViewer()
{
HorizontalScrollBarVisibility = ScrollBarVisibility.Auto;
}
@ -87,7 +83,7 @@ public class TabScrollViewer : ScrollViewer
{
base.OnPropertyChanged(change);
if (change.Property == TabStripPlacementProperty) {
if (Presenter is TabStripScrollContentPresenter tabStripScrollContentPresenter) {
if (Presenter is TabScrollContentPresenter tabStripScrollContentPresenter) {
tabStripScrollContentPresenter.TabStripPlacement = TabStripPlacement;
}
} else if (change.Property == VerticalScrollBarVisibilityProperty ||
@ -104,12 +100,14 @@ public class TabScrollViewer : ScrollViewer
if (TabStripPlacement == Dock.Top || TabStripPlacement == Dock.Bottom) {
if (_startEdgeIndicator is not null) {
_startEdgeIndicator.Height = Presenter.DesiredSize.Height;
_startEdgeIndicator.Width = _menuEdgeThickness;
_startEdgeIndicator.ZIndex = EdgeIndicatorZIndex;
_startEdgeIndicator.Background = BuildEdgeIndicatorBrush(TabStripPlacement, true);
}
if (_endEdgeIndicator is not null) {
_endEdgeIndicator.Height = Presenter.DesiredSize.Height;
_endEdgeIndicator.Width = _menuEdgeThickness;
_endEdgeIndicator.Margin = new Thickness(0, 0, _menuEdgeThickness, 0);
_endEdgeIndicator.ZIndex = EdgeIndicatorZIndex;
_endEdgeIndicator.Background = BuildEdgeIndicatorBrush(TabStripPlacement, false);
@ -117,13 +115,15 @@ public class TabScrollViewer : ScrollViewer
} else {
if (_startEdgeIndicator is not null) {
_startEdgeIndicator.Width = Presenter.DesiredSize.Width;
_startEdgeIndicator.Height = _menuEdgeThickness;
_startEdgeIndicator.ZIndex = EdgeIndicatorZIndex;
_startEdgeIndicator.Background = BuildEdgeIndicatorBrush(TabStripPlacement, false);
_startEdgeIndicator.Background = BuildEdgeIndicatorBrush(TabStripPlacement, true);
}
if (_endEdgeIndicator is not null) {
_endEdgeIndicator.Width = Presenter.DesiredSize.Width;
_endEdgeIndicator.Margin = new Thickness(0, _menuEdgeThickness, 0, 0);
_endEdgeIndicator.Height = _menuEdgeThickness;
_endEdgeIndicator.Margin = new Thickness(0, 0, 0, _menuEdgeThickness);
_endEdgeIndicator.ZIndex = EdgeIndicatorZIndex;
_endEdgeIndicator.Background = BuildEdgeIndicatorBrush(TabStripPlacement, false);
}
@ -163,7 +163,7 @@ public class TabScrollViewer : ScrollViewer
protected override bool RegisterContentPresenter(ContentPresenter presenter)
{
if (presenter is TabStripScrollContentPresenter tabStripScrollContentPresenter) {
if (presenter is TabScrollContentPresenter tabStripScrollContentPresenter) {
tabStripScrollContentPresenter.TabStripPlacement = TabStripPlacement;
}
@ -173,78 +173,16 @@ public class TabScrollViewer : ScrollViewer
protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
{
base.OnApplyTemplate(e);
_menuIndicator = e.NameScope.Find<IconButton>(TabScrollViewerTheme.ScrollMenuIndicatorPart);
_startEdgeIndicator = e.NameScope.Find<Border>(TabScrollViewerTheme.ScrollStartEdgeIndicatorPart);
_endEdgeIndicator = e.NameScope.Find<Border>(TabScrollViewerTheme.ScrollEndEdgeIndicatorPart);
_menuIndicator = e.NameScope.Find<IconButton>(BaseTabScrollViewerTheme.ScrollMenuIndicatorPart);
_startEdgeIndicator = e.NameScope.Find<Border>(BaseTabScrollViewerTheme.ScrollStartEdgeIndicatorPart);
_endEdgeIndicator = e.NameScope.Find<Border>(BaseTabScrollViewerTheme.ScrollEndEdgeIndicatorPart);
TokenResourceBinder.CreateTokenBinding(this, EdgeShadowStartColorProperty, GlobalResourceKey.ColorFillSecondary);
TokenResourceBinder.CreateTokenBinding(this, MenuEdgeThicknessProperty, TabControlResourceKey.MenuEdgeThickness);
if (_menuIndicator is not null) {
_menuIndicator.Click += HandleMenuIndicator;
}
SetupIndicatorsVisibility();
}
private void HandleMenuIndicator(object? sender, RoutedEventArgs args)
{
if (_menuFlyout is null) {
_menuFlyout = new MenuFlyout();
}
if (TabStripPlacement == Dock.Top || TabStripPlacement == Dock.Bottom) {
_menuFlyout.Placement = PlacementMode.Bottom;
} else {
_menuFlyout.Placement = PlacementMode.Right;
}
// 收集没有完全显示的 Tab 列表
_menuFlyout.Items.Clear();
if (TabStrip is not null) {
for (int i = 0; i < TabStrip.ItemCount; i++) {
var itemContainer = TabStrip.ContainerFromIndex(i)!;
if (itemContainer is TabStripItem tabStripItem) {
var itemBounds = itemContainer.Bounds;
var left = Math.Floor(itemBounds.Left - Offset.X);
var right = Math.Floor(itemBounds.Right - Offset.X);
if (TabStripPlacement == Dock.Top || TabStripPlacement == Dock.Bottom) {
if (left < 0 || right > Viewport.Width) {
var menuItem = new TabStripMenuItem()
{
Header = tabStripItem.Content,
TabStripItem = tabStripItem,
IsClosable = tabStripItem.IsClosable
};
menuItem.Click += HandleMenuItemClicked;
_menuFlyout.Items.Add(menuItem);
}
}
}
}
if (_menuFlyout.Items.Count > 0) {
_menuFlyout.ShowAt(_menuIndicator!);
}
}
}
private void HandleMenuItemClicked(object? sender, RoutedEventArgs args)
{
if (TabStrip is not null) {
Dispatcher.UIThread.Post(sender =>
{
if (sender is TabStripMenuItem tabStripMenuItem) {
var tabStripItem = tabStripMenuItem.TabStripItem;
if (tabStripItem is not null) {
tabStripItem.BringIntoView();
TabStrip.SelectedItem = tabStripItem;
}
}
}, sender);
}
}
protected override Size MeasureOverride(Size availableSize)
{
var size = base.MeasureOverride(availableSize);

View File

@ -13,21 +13,21 @@ using Avalonia.Styling;
namespace AtomUI.Controls;
[ControlThemeProvider]
internal class TabScrollViewerTheme : BaseControlTheme
internal class BaseTabScrollViewerTheme : BaseControlTheme
{
public const string ScrollStartEdgeIndicatorPart = "Part_ScrollStartEdgeIndicator";
public const string ScrollEndEdgeIndicatorPart = "Part_ScrollEndEdgeIndicator";
public const string ScrollMenuIndicatorPart = "Part_ScrollMenuIndicator";
public const string ScrollViewContentPart = "PART_ContentPresenter";
public const string ScrollViewLayoutPart = "PART_ScrollViewLayout";
public const string ScrollViewWrapperLayoutPart = "PART_ScrollViewWrapperLayout";
public const string ScrollStartEdgeIndicatorPart = "PART_ScrollStartEdgeIndicator";
public const string ScrollEndEdgeIndicatorPart = "PART_ScrollEndEdgeIndicator";
public const string ScrollMenuIndicatorPart = "PART_ScrollMenuIndicator";
public const string ScrollViewContentPart = "PART_ContentPresenter";
public const string ScrollViewLayoutPart = "PART_ScrollViewLayout";
public const string ScrollViewWrapperLayoutPart = "PART_ScrollViewWrapperLayout";
public TabScrollViewerTheme()
: base(typeof(TabScrollViewer)) { }
public BaseTabScrollViewerTheme()
: base(typeof(BaseTabScrollViewer)) { }
protected override IControlTemplate BuildControlTemplate()
{
return new FuncControlTemplate<TabScrollViewer>((scrollViewer, scope) =>
return new FuncControlTemplate<BaseTabScrollViewer>((scrollViewer, scope) =>
{
scrollViewer.HorizontalScrollBarVisibility = ScrollBarVisibility.Auto;
var containerLayout = new Panel()
@ -61,9 +61,6 @@ internal class TabScrollViewerTheme : BaseControlTheme
var scrollViewContent = CreateScrollContentPresenter();
DockPanel.SetDock(scrollViewContent, Dock.Left);
DockPanel.SetDock(menuIndicator, Dock.Right);
scrollViewLayout.Children.Add(menuIndicator);
scrollViewLayout.Children.Add(scrollViewContent);
@ -72,8 +69,6 @@ internal class TabScrollViewerTheme : BaseControlTheme
var startEdgeIndicator = new Border()
{
Name = ScrollStartEdgeIndicatorPart,
HorizontalAlignment = HorizontalAlignment.Left,
VerticalAlignment = VerticalAlignment.Top,
IsHitTestVisible = false
};
startEdgeIndicator.RegisterInNameScope(scope);
@ -82,8 +77,6 @@ internal class TabScrollViewerTheme : BaseControlTheme
var endEdgeIndicator = new Border()
{
HorizontalAlignment = HorizontalAlignment.Right,
VerticalAlignment = VerticalAlignment.Top,
Name = ScrollEndEdgeIndicatorPart,
IsHitTestVisible = false
};
@ -98,21 +91,21 @@ internal class TabScrollViewerTheme : BaseControlTheme
private ScrollContentPresenter CreateScrollContentPresenter()
{
var scrollViewContent = new TabStripScrollContentPresenter()
var scrollViewContent = new TabScrollContentPresenter()
{
Name = ScrollViewContentPart,
};
CreateTemplateParentBinding(scrollViewContent, ScrollContentPresenter.MarginProperty,
TabScrollViewer.PaddingProperty);
BaseTabScrollViewer.PaddingProperty);
CreateTemplateParentBinding(scrollViewContent, ScrollContentPresenter.HorizontalSnapPointsAlignmentProperty,
TabScrollViewer.HorizontalSnapPointsAlignmentProperty);
BaseTabScrollViewer.HorizontalSnapPointsAlignmentProperty);
CreateTemplateParentBinding(scrollViewContent, ScrollContentPresenter.HorizontalSnapPointsTypeProperty,
TabScrollViewer.HorizontalSnapPointsTypeProperty);
BaseTabScrollViewer.HorizontalSnapPointsTypeProperty);
CreateTemplateParentBinding(scrollViewContent, ScrollContentPresenter.VerticalSnapPointsAlignmentProperty,
TabScrollViewer.VerticalSnapPointsAlignmentProperty);
BaseTabScrollViewer.VerticalSnapPointsAlignmentProperty);
CreateTemplateParentBinding(scrollViewContent, ScrollContentPresenter.VerticalSnapPointsTypeProperty,
TabScrollViewer.VerticalSnapPointsTypeProperty);
BaseTabScrollViewer.VerticalSnapPointsTypeProperty);
var scrollGestureRecognizer = new ScrollGestureRecognizer();
BindUtils.RelayBind(scrollViewContent, ScrollContentPresenter.CanHorizontallyScrollProperty, scrollGestureRecognizer,
ScrollGestureRecognizer.CanHorizontallyScrollProperty);
@ -120,7 +113,7 @@ internal class TabScrollViewerTheme : BaseControlTheme
ScrollGestureRecognizer.CanVerticallyScrollProperty);
CreateTemplateParentBinding(scrollGestureRecognizer, ScrollGestureRecognizer.IsScrollInertiaEnabledProperty,
TabScrollViewer.IsScrollInertiaEnabledProperty);
BaseTabScrollViewer.IsScrollInertiaEnabledProperty);
scrollViewContent.GestureRecognizers.Add(scrollGestureRecognizer);
return scrollViewContent;
@ -128,22 +121,27 @@ internal class TabScrollViewerTheme : BaseControlTheme
protected override void BuildStyles()
{
var topPlacementStyle = new Style(selector => selector.Nesting().PropertyEquals(TabScrollViewer.TabStripPlacementProperty, Dock.Top));
var topPlacementStyle = new Style(selector => selector.Nesting().PropertyEquals(BaseTabScrollViewer.TabStripPlacementProperty, Dock.Top));
{
var contentPresenterStyle =
new Style(selector => selector.Nesting().Template().OfType<TabStripScrollContentPresenter>());
new Style(selector => selector.Nesting().Template().Name(ScrollViewContentPart));
contentPresenterStyle.Add(DockPanel.DockProperty, Dock.Left);
var menuIndicatorStyle =
new Style(selector => selector.Nesting().Template().Name(ScrollMenuIndicatorPart));
menuIndicatorStyle.Add(DockPanel.DockProperty, Dock.Right);
menuIndicatorStyle.Add(IconButton.PaddingProperty, TabControlResourceKey.MenuIndicatorPaddingHorizontal);
menuIndicatorStyle.Add(TemplatedControl.PaddingProperty, TabControlResourceKey.MenuIndicatorPaddingHorizontal);
var startEdgeIndicatorStyle = new Style(selector => selector.Nesting().Template().Name(ScrollStartEdgeIndicatorPart));
startEdgeIndicatorStyle.Add(Control.WidthProperty, TabControlResourceKey.MenuEdgeThickness);
startEdgeIndicatorStyle.Add(Layoutable.WidthProperty, TabControlResourceKey.MenuEdgeThickness);
startEdgeIndicatorStyle.Add(Border.HorizontalAlignmentProperty, HorizontalAlignment.Left);
startEdgeIndicatorStyle.Add(Border.VerticalAlignmentProperty, VerticalAlignment.Stretch);
topPlacementStyle.Add(startEdgeIndicatorStyle);
var endEdgeIndicatorStyle = new Style(selector => selector.Nesting().Template().Name(ScrollEndEdgeIndicatorPart));
endEdgeIndicatorStyle.Add(Control.WidthProperty, TabControlResourceKey.MenuEdgeThickness);
endEdgeIndicatorStyle.Add(Layoutable.WidthProperty, TabControlResourceKey.MenuEdgeThickness);
endEdgeIndicatorStyle.Add(Border.HorizontalAlignmentProperty, HorizontalAlignment.Right);
endEdgeIndicatorStyle.Add(Border.VerticalAlignmentProperty, VerticalAlignment.Stretch);
topPlacementStyle.Add(endEdgeIndicatorStyle);
topPlacementStyle.Add(menuIndicatorStyle);
@ -152,24 +150,28 @@ internal class TabScrollViewerTheme : BaseControlTheme
Add(topPlacementStyle);
var rightPlacementStyle = new Style(selector => selector.Nesting().PropertyEquals(TabScrollViewer.TabStripPlacementProperty, Dock.Right));
var rightPlacementStyle = new Style(selector => selector.Nesting().PropertyEquals(BaseTabScrollViewer.TabStripPlacementProperty, Dock.Right));
{
var contentPresenterStyle =
new Style(selector => selector.Nesting().Template().OfType<TabStripScrollContentPresenter>());
new Style(selector => selector.Nesting().Template().Name(ScrollViewContentPart));
contentPresenterStyle.Add(DockPanel.DockProperty, Dock.Top);
var menuIndicatorStyle =
new Style(selector => selector.Nesting().Template().Name(ScrollMenuIndicatorPart));
menuIndicatorStyle.Add(DockPanel.DockProperty, Dock.Bottom);
menuIndicatorStyle.Add(IconButton.PaddingProperty, TabControlResourceKey.MenuIndicatorPaddingVertical);
menuIndicatorStyle.Add(TemplatedControl.PaddingProperty, TabControlResourceKey.MenuIndicatorPaddingVertical);
var startEdgeIndicatorStyle = new Style(selector => selector.Nesting().Template().Name(ScrollStartEdgeIndicatorPart));
startEdgeIndicatorStyle.Add(Control.HeightProperty, TabControlResourceKey.MenuEdgeThickness);
topPlacementStyle.Add(startEdgeIndicatorStyle);
startEdgeIndicatorStyle.Add(Layoutable.HeightProperty, TabControlResourceKey.MenuEdgeThickness);
startEdgeIndicatorStyle.Add(Border.HorizontalAlignmentProperty, HorizontalAlignment.Stretch);
startEdgeIndicatorStyle.Add(Border.VerticalAlignmentProperty, VerticalAlignment.Top);
rightPlacementStyle.Add(startEdgeIndicatorStyle);
var endEdgeIndicatorStyle = new Style(selector => selector.Nesting().Template().Name(ScrollStartEdgeIndicatorPart));
endEdgeIndicatorStyle.Add(Control.HeightProperty, TabControlResourceKey.MenuEdgeThickness);
topPlacementStyle.Add(endEdgeIndicatorStyle);
var endEdgeIndicatorStyle = new Style(selector => selector.Nesting().Template().Name(ScrollEndEdgeIndicatorPart));
endEdgeIndicatorStyle.Add(Layoutable.HeightProperty, TabControlResourceKey.MenuEdgeThickness);
endEdgeIndicatorStyle.Add(Border.HorizontalAlignmentProperty, HorizontalAlignment.Stretch);
endEdgeIndicatorStyle.Add(Border.VerticalAlignmentProperty, VerticalAlignment.Bottom);
rightPlacementStyle.Add(endEdgeIndicatorStyle);
rightPlacementStyle.Add(menuIndicatorStyle);
rightPlacementStyle.Add(contentPresenterStyle);
@ -177,24 +179,28 @@ internal class TabScrollViewerTheme : BaseControlTheme
Add(rightPlacementStyle);
var bottomPlacementStyle = new Style(selector => selector.Nesting().PropertyEquals(TabScrollViewer.TabStripPlacementProperty, Dock.Bottom));
var bottomPlacementStyle = new Style(selector => selector.Nesting().PropertyEquals(BaseTabScrollViewer.TabStripPlacementProperty, Dock.Bottom));
{
var contentPresenterStyle =
new Style(selector => selector.Nesting().Template().OfType<TabStripScrollContentPresenter>());
new Style(selector => selector.Nesting().Template().Name(ScrollViewContentPart));
contentPresenterStyle.Add(DockPanel.DockProperty, Dock.Left);
var menuIndicatorStyle =
new Style(selector => selector.Nesting().Template().Name(ScrollMenuIndicatorPart));
menuIndicatorStyle.Add(DockPanel.DockProperty, Dock.Right);
menuIndicatorStyle.Add(IconButton.PaddingProperty, TabControlResourceKey.MenuIndicatorPaddingHorizontal);
menuIndicatorStyle.Add(TemplatedControl.PaddingProperty, TabControlResourceKey.MenuIndicatorPaddingHorizontal);
var startEdgeIndicatorStyle = new Style(selector => selector.Nesting().Template().Name(ScrollStartEdgeIndicatorPart));
startEdgeIndicatorStyle.Add(Control.WidthProperty, TabControlResourceKey.MenuEdgeThickness);
topPlacementStyle.Add(startEdgeIndicatorStyle);
startEdgeIndicatorStyle.Add(Layoutable.WidthProperty, TabControlResourceKey.MenuEdgeThickness);
startEdgeIndicatorStyle.Add(Border.HorizontalAlignmentProperty, HorizontalAlignment.Left);
startEdgeIndicatorStyle.Add(Border.VerticalAlignmentProperty, VerticalAlignment.Stretch);
bottomPlacementStyle.Add(startEdgeIndicatorStyle);
var endEdgeIndicatorStyle = new Style(selector => selector.Nesting().Template().Name(ScrollStartEdgeIndicatorPart));
endEdgeIndicatorStyle.Add(Control.WidthProperty, TabControlResourceKey.MenuEdgeThickness);
topPlacementStyle.Add(endEdgeIndicatorStyle);
var endEdgeIndicatorStyle = new Style(selector => selector.Nesting().Template().Name(ScrollEndEdgeIndicatorPart));
endEdgeIndicatorStyle.Add(Layoutable.WidthProperty, TabControlResourceKey.MenuEdgeThickness);
endEdgeIndicatorStyle.Add(Border.HorizontalAlignmentProperty, HorizontalAlignment.Right);
endEdgeIndicatorStyle.Add(Border.VerticalAlignmentProperty, VerticalAlignment.Stretch);
bottomPlacementStyle.Add(endEdgeIndicatorStyle);
bottomPlacementStyle.Add(menuIndicatorStyle);
bottomPlacementStyle.Add(contentPresenterStyle);
@ -202,24 +208,28 @@ internal class TabScrollViewerTheme : BaseControlTheme
Add(bottomPlacementStyle);
var leftPlacementStyle = new Style(selector => selector.Nesting().PropertyEquals(TabScrollViewer.TabStripPlacementProperty, Dock.Left));
var leftPlacementStyle = new Style(selector => selector.Nesting().PropertyEquals(BaseTabScrollViewer.TabStripPlacementProperty, Dock.Left));
{
var contentPresenterStyle =
new Style(selector => selector.Nesting().Template().OfType<TabStripScrollContentPresenter>());
new Style(selector => selector.Nesting().Template().Name(ScrollViewContentPart));
contentPresenterStyle.Add(DockPanel.DockProperty, Dock.Top);
var menuIndicatorStyle =
new Style(selector => selector.Nesting().Template().Name(ScrollMenuIndicatorPart));
menuIndicatorStyle.Add(DockPanel.DockProperty, Dock.Bottom);
menuIndicatorStyle.Add(IconButton.PaddingProperty, TabControlResourceKey.MenuIndicatorPaddingVertical);
menuIndicatorStyle.Add(TemplatedControl.PaddingProperty, TabControlResourceKey.MenuIndicatorPaddingVertical);
var startEdgeIndicatorStyle = new Style(selector => selector.Nesting().Template().Name(ScrollStartEdgeIndicatorPart));
startEdgeIndicatorStyle.Add(Control.HeightProperty, TabControlResourceKey.MenuEdgeThickness);
topPlacementStyle.Add(startEdgeIndicatorStyle);
startEdgeIndicatorStyle.Add(Layoutable.HeightProperty, TabControlResourceKey.MenuEdgeThickness);
startEdgeIndicatorStyle.Add(Border.HorizontalAlignmentProperty, HorizontalAlignment.Stretch);
startEdgeIndicatorStyle.Add(Border.VerticalAlignmentProperty, VerticalAlignment.Top);
leftPlacementStyle.Add(startEdgeIndicatorStyle);
var endEdgeIndicatorStyle = new Style(selector => selector.Nesting().Template().Name(ScrollStartEdgeIndicatorPart));
endEdgeIndicatorStyle.Add(Control.HeightProperty, TabControlResourceKey.MenuEdgeThickness);
topPlacementStyle.Add(endEdgeIndicatorStyle);
var endEdgeIndicatorStyle = new Style(selector => selector.Nesting().Template().Name(ScrollEndEdgeIndicatorPart));
endEdgeIndicatorStyle.Add(Layoutable.HeightProperty, TabControlResourceKey.MenuEdgeThickness);
endEdgeIndicatorStyle.Add(Border.HorizontalAlignmentProperty, HorizontalAlignment.Stretch);
endEdgeIndicatorStyle.Add(Border.VerticalAlignmentProperty, VerticalAlignment.Bottom);
leftPlacementStyle.Add(endEdgeIndicatorStyle);
leftPlacementStyle.Add(menuIndicatorStyle);
leftPlacementStyle.Add(contentPresenterStyle);

View File

@ -1,41 +0,0 @@
using AtomUI.Theme;
using AtomUI.Theme.Styling;
using Avalonia.Controls;
using Avalonia.Controls.Templates;
using Avalonia.Styling;
namespace AtomUI.Controls;
internal class BaseTabStripTheme : BaseControlTheme
{
public const string MainContainerPart = "Part_MainContainer";
public const string ItemsPresenterPart = "PART_ItemsPresenter";
public BaseTabStripTheme(Type targetType) : base(targetType) { }
protected override IControlTemplate BuildControlTemplate()
{
return new FuncControlTemplate<BaseTabStrip>((strip, scope) =>
{
var mainContainer = new Border()
{
Name = MainContainerPart
};
mainContainer.RegisterInNameScope(scope);
NotifyBuildControlTemplate(strip, scope, mainContainer);
return mainContainer;
});
}
protected virtual void NotifyBuildControlTemplate(BaseTabStrip baseTabStrip, INameScope scope, Border container)
{
}
protected override void BuildStyles()
{
base.BuildStyles();
var commonStyle = new Style(selector => selector.Nesting());
commonStyle.Add(BaseTabStrip.BorderBrushProperty, GlobalResourceKey.ColorBorderSecondary);
Add(commonStyle);
}
}

View File

@ -0,0 +1,202 @@
using AtomUI.Data;
using AtomUI.Theme.Data;
using AtomUI.Theme.Styling;
using AtomUI.Utils;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.Presenters;
using Avalonia.Controls.Primitives;
using Avalonia.Data;
using Avalonia.Interactivity;
using Avalonia.Layout;
namespace AtomUI.Controls;
public class CardTabControl : BaseTabControl, IControlCustomStyle
{
#region
public readonly static StyledProperty<bool> IsShowAddTabButtonProperty =
AvaloniaProperty.Register<CardTabControl, bool>(nameof(IsShowAddTabButton), false);
public readonly static RoutedEvent<RoutedEventArgs> AddTabRequestEvent =
RoutedEvent.Register<CardTabControl, RoutedEventArgs>(
nameof(AddTabRequest),
RoutingStrategies.Bubble);
public bool IsShowAddTabButton
{
get => GetValue(IsShowAddTabButtonProperty);
set => SetValue(IsShowAddTabButtonProperty, value);
}
public event EventHandler<RoutedEventArgs>? AddTabRequest
{
add => AddHandler(AddTabRequestEvent, value);
remove => RemoveHandler(AddTabRequestEvent, value);
}
#endregion
#region
internal static readonly StyledProperty<CornerRadius> CardBorderRadiusProperty =
AvaloniaProperty.Register<CardTabControl, CornerRadius>(nameof(CardBorderRadius));
internal static readonly StyledProperty<Thickness> CardBorderThicknessProperty =
AvaloniaProperty.Register<CardTabControl, Thickness>(nameof(CardBorderThickness));
internal static readonly DirectProperty<CardTabControl, CornerRadius> CardBorderRadiusSizeProperty =
AvaloniaProperty.RegisterDirect<CardTabControl, CornerRadius>(nameof(CardBorderRadiusSize),
o => o.CardBorderRadiusSize,
(o, v) => o.CardBorderRadiusSize = v);
internal static readonly DirectProperty<CardTabControl, double> CardSizeProperty =
AvaloniaProperty.RegisterDirect<CardTabControl, double>(nameof(CardSize),
o => o.CardSize,
(o, v) => o.CardSize = v);
internal CornerRadius CardBorderRadius
{
get => GetValue(CardBorderRadiusProperty);
set => SetValue(CardBorderRadiusProperty, value);
}
internal Thickness CardBorderThickness
{
get => GetValue(CardBorderThicknessProperty);
set => SetValue(CardBorderThicknessProperty, value);
}
private CornerRadius _cardBorderRadiusSize;
internal CornerRadius CardBorderRadiusSize
{
get => _cardBorderRadiusSize;
set => SetAndRaise(CardBorderRadiusSizeProperty, ref _cardBorderRadiusSize, value);
}
private double _cardSize;
internal double CardSize
{
get => _cardSize;
set => SetAndRaise(CardSizeProperty, ref _cardSize, value);
}
#endregion
private IControlCustomStyle _customStyle;
private IconButton? _addTabButton;
private ItemsPresenter? _itemsPresenter;
public CardTabControl()
{
_customStyle = this;
}
protected override Control CreateContainerForItemOverride(object? item, int index, object? recycleKey)
{
return new TabItem
{
Shape = TabSharp.Card
};
}
protected override void PrepareContainerForItemOverride(Control container, object? item, int index)
{
base.PrepareContainerForItemOverride(container, item, index);
if (container is TabItem tabItem) {
tabItem.Shape = TabSharp.Card;
BindUtils.RelayBind(this, CardBorderRadiusProperty, tabItem, TabItem.CornerRadiusProperty);
BindUtils.RelayBind(this, CardBorderThicknessProperty, tabItem, TabItem.BorderThicknessProperty);
}
}
protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
{
base.OnApplyTemplate(e);
TokenResourceBinder.CreateTokenBinding(this, CardBorderThicknessProperty, GlobalResourceKey.BorderThickness, BindingPriority.Template,
new RenderScaleAwareThicknessConfigure(this));
TokenResourceBinder.CreateTokenBinding(this, CardSizeProperty, TabControlResourceKey.CardSize);
_customStyle.HandleTemplateApplied(e.NameScope);
}
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
{
base.OnPropertyChanged(change);
if (change.Property == SizeTypeProperty) {
HandleSizeTypeChanged();
} else if (change.Property == TabStripPlacementProperty) {
HandleTabStripPlacementChanged();
}
}
#region IControlCustomStyle
void IControlCustomStyle.HandleTemplateApplied(INameScope scope)
{
_addTabButton = scope.Find<IconButton>(CardTabControlTheme.AddTabButtonPart);
_itemsPresenter = scope.Find<ItemsPresenter>(CardTabControlTheme.ItemsPresenterPart);
if (_addTabButton is not null) {
_addTabButton.Click += HandleAddButtonClicked;
}
HandleSizeTypeChanged();
}
#endregion
private void HandleAddButtonClicked(object? sender, RoutedEventArgs args)
{
RaiseEvent(new RoutedEventArgs(AddTabRequestEvent));
}
private void HandleSizeTypeChanged()
{
if (SizeType == SizeType.Large) {
TokenResourceBinder.CreateGlobalResourceBinding(this, CardBorderRadiusSizeProperty, GlobalResourceKey.BorderRadiusLG);
} else if (SizeType == SizeType.Middle) {
TokenResourceBinder.CreateGlobalResourceBinding(this, CardBorderRadiusSizeProperty, GlobalResourceKey.BorderRadius);
} else {
TokenResourceBinder.CreateGlobalResourceBinding(this, CardBorderRadiusSizeProperty, GlobalResourceKey.BorderRadiusSM);
}
}
protected override Size ArrangeOverride(Size finalSize)
{
var size = base.ArrangeOverride(finalSize);
HandleTabStripPlacementChanged();
return size;
}
private void HandleTabStripPlacementChanged()
{
if (TabStripPlacement == Dock.Top) {
CardBorderRadius = new CornerRadius(topLeft: _cardBorderRadiusSize.TopLeft,
topRight: _cardBorderRadiusSize.TopRight, bottomLeft: 0, bottomRight: 0);
if (_addTabButton is not null && _itemsPresenter is not null) {
_addTabButton.Width = _cardSize;
_addTabButton.Height = _itemsPresenter.DesiredSize.Height;
}
} else if (TabStripPlacement == Dock.Bottom) {
CardBorderRadius = new CornerRadius(topLeft: 0, 0, bottomLeft: _cardBorderRadiusSize.BottomLeft,
bottomRight: _cardBorderRadiusSize.BottomRight);
if (_addTabButton is not null && _itemsPresenter is not null) {
_addTabButton.Width = _cardSize;
_addTabButton.Height = _itemsPresenter.DesiredSize.Height;
}
} else if (TabStripPlacement == Dock.Left) {
CardBorderRadius = new CornerRadius(topLeft: _cardBorderRadiusSize.TopLeft, 0,
bottomLeft: _cardBorderRadiusSize.BottomLeft, bottomRight: 0);
if (_addTabButton is not null && _itemsPresenter is not null) {
_addTabButton.Width = _cardSize;
_addTabButton.Height = _cardSize;
_addTabButton.HorizontalAlignment = HorizontalAlignment.Right;
}
} else {
CardBorderRadius = new CornerRadius(topLeft: 0, topRight: _cardBorderRadiusSize.TopRight, bottomLeft: 0,
bottomRight: _cardBorderRadiusSize.BottomRight);
if (_addTabButton is not null && _itemsPresenter is not null) {
_addTabButton.Width = _cardSize;
_addTabButton.Height = _cardSize;
_addTabButton.HorizontalAlignment = HorizontalAlignment.Left;
}
}
}
}

View File

@ -0,0 +1,180 @@
using AtomUI.Theme.Styling;
using AtomUI.Utils;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.Presenters;
using Avalonia.Controls.Templates;
using Avalonia.Layout;
using Avalonia.Styling;
namespace AtomUI.Controls;
[ControlThemeProvider]
internal class CardTabControlTheme : BaseTabControlTheme
{
public const string AddTabButtonPart = "PART_AddTabButton";
public const string CardTabStripScrollViewerPart = "PART_CardTabStripScrollViewer";
public CardTabControlTheme() : base(typeof(CardTabControl)) { }
protected override void NotifyBuildTabStripTemplate(BaseTabControl baseTabControl, INameScope scope, DockPanel container)
{
var alignWrapper = new Panel()
{
Name = AlignWrapperPart
};
alignWrapper.RegisterInNameScope(scope);
CreateTemplateParentBinding(alignWrapper, DockPanel.DockProperty, BaseTabControl.TabStripPlacementProperty);
CreateTemplateParentBinding(alignWrapper, Panel.MarginProperty,TabControl.TabStripMarginProperty);
var cardTabControlContainer = new TabsContainerPanel()
{
Name = TabsContainerPart,
};
cardTabControlContainer.RegisterInNameScope(scope);
CreateTemplateParentBinding(cardTabControlContainer, TabsContainerPanel.TabStripPlacementProperty, TabStrip.TabStripPlacementProperty);
var tabScrollViewer = new TabControlScrollViewer()
{
Name = CardTabStripScrollViewerPart
};
CreateTemplateParentBinding(tabScrollViewer, TabControlScrollViewer.TabStripPlacementProperty, BaseTabControl.TabStripPlacementProperty);
tabScrollViewer.RegisterInNameScope(scope);
var contentPanel = CreateTabStripContentPanel(scope);
tabScrollViewer.Content = contentPanel;
tabScrollViewer.TabControl = baseTabControl;
var addTabIcon = new PathIcon()
{
Kind = "PlusOutlined"
};
TokenResourceBinder.CreateTokenBinding(addTabIcon, PathIcon.NormalFilledBrushProperty, TabControlResourceKey.ItemColor);
TokenResourceBinder.CreateTokenBinding(addTabIcon, PathIcon.ActiveFilledBrushProperty, TabControlResourceKey.ItemHoverColor);
TokenResourceBinder.CreateTokenBinding(addTabIcon, PathIcon.DisabledFilledBrushProperty, GlobalResourceKey.ColorTextDisabled);
TokenResourceBinder.CreateGlobalResourceBinding(addTabIcon, PathIcon.WidthProperty, GlobalResourceKey.IconSize);
TokenResourceBinder.CreateGlobalResourceBinding(addTabIcon, PathIcon.HeightProperty, GlobalResourceKey.IconSize);
var addTabButton = new IconButton
{
Name = AddTabButtonPart,
BorderThickness = new Thickness(1),
Icon = addTabIcon
};
DockPanel.SetDock(addTabButton, Dock.Right);
CreateTemplateParentBinding(addTabButton, IconButton.BorderThicknessProperty, CardTabControl.CardBorderThicknessProperty);
CreateTemplateParentBinding(addTabButton, IconButton.CornerRadiusProperty, CardTabControl.CardBorderRadiusProperty);
CreateTemplateParentBinding(addTabButton, IconButton.IsVisibleProperty, CardTabControl.IsShowAddTabButtonProperty);
TokenResourceBinder.CreateGlobalResourceBinding(addTabButton, IconButton.BorderBrushProperty, GlobalResourceKey.ColorBorderSecondary);
addTabButton.RegisterInNameScope(scope);
cardTabControlContainer.TabScrollViewer = tabScrollViewer;
cardTabControlContainer.AddTabButton = addTabButton;
alignWrapper.Children.Add(cardTabControlContainer);
container.Children.Add(alignWrapper);
}
private ItemsPresenter CreateTabStripContentPanel(INameScope scope)
{
var itemsPresenter = new ItemsPresenter
{
Name = ItemsPresenterPart,
};
itemsPresenter.RegisterInNameScope(scope);
CreateTemplateParentBinding(itemsPresenter, ItemsPresenter.ItemsPanelProperty, TabControl.ItemsPanelProperty);
return itemsPresenter;
}
protected override void BuildStyles()
{
base.BuildStyles();
var commonStyle = new Style(selector => selector.Nesting());
// 设置 items presenter 面板样式
// 分为上、右、下、左
{
// 上
var topStyle = new Style(selector => selector.Nesting().Class(BaseTabControl.TopPC));
var containerStyle = new Style(selector => selector.Nesting().Template().Name(TabsContainerPart));
topStyle.Add(containerStyle);
var itemPresenterPanelStyle = new Style(selector => selector.Nesting().Template().Name(ItemsPresenterPart).Child().OfType<StackPanel>());
itemPresenterPanelStyle.Add(StackPanel.OrientationProperty, Orientation.Horizontal);
itemPresenterPanelStyle.Add(StackPanel.SpacingProperty, TabControlResourceKey.CardGutter);
var addTabButtonStyle = new Style(selector => selector.Nesting().Template().Name(AddTabButtonPart));
addTabButtonStyle.Add(IconButton.MarginProperty, TabControlResourceKey.AddTabButtonMarginHorizontal);
topStyle.Add(addTabButtonStyle);
topStyle.Add(itemPresenterPanelStyle);
commonStyle.Add(topStyle);
}
{
// 右
var rightStyle = new Style(selector => selector.Nesting().Class(BaseTabControl.RightPC));
var containerStyle = new Style(selector => selector.Nesting().Template().Name(TabsContainerPart));
containerStyle.Add(StackPanel.OrientationProperty, Orientation.Vertical);
rightStyle.Add(containerStyle);
var itemPresenterPanelStyle = new Style(selector => selector.Nesting().Template().Name(ItemsPresenterPart).Child().OfType<StackPanel>());
itemPresenterPanelStyle.Add(StackPanel.OrientationProperty, Orientation.Vertical);
itemPresenterPanelStyle.Add(StackPanel.SpacingProperty, TabControlResourceKey.CardGutter);
rightStyle.Add(itemPresenterPanelStyle);
var addTabButtonStyle = new Style(selector => selector.Nesting().Template().Name(AddTabButtonPart));
addTabButtonStyle.Add(IconButton.MarginProperty, TabControlResourceKey.AddTabButtonMarginVertical);
rightStyle.Add(addTabButtonStyle);
commonStyle.Add(rightStyle);
}
{
// 下
var bottomStyle = new Style(selector => selector.Nesting().Class(BaseTabControl.BottomPC));
var containerStyle = new Style(selector => selector.Nesting().Template().Name(TabsContainerPart));
containerStyle.Add(StackPanel.OrientationProperty, Orientation.Horizontal);
bottomStyle.Add(containerStyle);
var itemPresenterPanelStyle = new Style(selector => selector.Nesting().Template().Name(ItemsPresenterPart).Child().OfType<StackPanel>());
itemPresenterPanelStyle.Add(StackPanel.OrientationProperty, Orientation.Horizontal);
itemPresenterPanelStyle.Add(StackPanel.SpacingProperty, TabControlResourceKey.CardGutter);
bottomStyle.Add(itemPresenterPanelStyle);
var addTabButtonStyle = new Style(selector => selector.Nesting().Template().Name(AddTabButtonPart));
addTabButtonStyle.Add(IconButton.MarginProperty, TabControlResourceKey.AddTabButtonMarginHorizontal);
bottomStyle.Add(addTabButtonStyle);
commonStyle.Add(bottomStyle);
}
{
// 左
var leftStyle = new Style(selector => selector.Nesting().Class(BaseTabControl.LeftPC));
var containerStyle = new Style(selector => selector.Nesting().Template().Name(TabsContainerPart));
containerStyle.Add(StackPanel.OrientationProperty, Orientation.Vertical);
leftStyle.Add(containerStyle);
var itemPresenterPanelStyle = new Style(selector => selector.Nesting().Template().Name(ItemsPresenterPart).Child().OfType<StackPanel>());
itemPresenterPanelStyle.Add(StackPanel.OrientationProperty, Orientation.Vertical);
itemPresenterPanelStyle.Add(StackPanel.SpacingProperty, TabControlResourceKey.CardGutter);
leftStyle.Add(itemPresenterPanelStyle);
var addTabButtonStyle = new Style(selector => selector.Nesting().Template().Name(AddTabButtonPart));
addTabButtonStyle.Add(IconButton.MarginProperty, TabControlResourceKey.AddTabButtonMarginVertical);
leftStyle.Add(addTabButtonStyle);
commonStyle.Add(leftStyle);
}
Add(commonStyle);
}
}

View File

@ -0,0 +1,181 @@
using AtomUI.Media;
using AtomUI.Theme.Styling;
using AtomUI.Theme.Utils;
using Avalonia.Animation;
using Avalonia.Controls;
using Avalonia.Layout;
using Avalonia.Styling;
namespace AtomUI.Controls;
[ControlThemeProvider]
internal class CardTabItemTheme : BaseTabItemTheme
{
public const string ID = "CardTabItem";
public CardTabItemTheme() : base(typeof(TabItem)) { }
public override string ThemeResourceKey()
{
return ID;
}
protected override void NotifyBuildControlTemplate(TabItem tabItem, INameScope scope, Border container)
{
base.NotifyBuildControlTemplate(tabItem, scope, container);
if (container.Transitions is null) {
var transitions = new Transitions();
transitions.Add(AnimationUtils.CreateTransition<SolidColorBrushTransition>(Border.BackgroundProperty));
container.Transitions = transitions;
}
CreateTemplateParentBinding(container, Border.BorderThicknessProperty, TabItem.BorderThicknessProperty);
CreateTemplateParentBinding(container, Border.CornerRadiusProperty, TabItem.CornerRadiusProperty);
}
protected override void BuildStyles()
{
base.BuildStyles();
var commonStyle = new Style(selector => selector.Nesting());
{
var decoratorStyle = new Style(selector => selector.Nesting().Template().Name(DecoratorPart));
decoratorStyle.Add(Border.MarginProperty, TabControlResourceKey.HorizontalItemMargin);
decoratorStyle.Add(Border.BackgroundProperty, TabControlResourceKey.CardBg);
decoratorStyle.Add(Border.BorderBrushProperty, GlobalResourceKey.ColorBorderSecondary);
commonStyle.Add(decoratorStyle);
}
// 选中
var selectedStyle = new Style(selector => selector.Nesting().Class(StdPseudoClass.Selected));
{
var decoratorStyle = new Style(selector => selector.Nesting().Template().Name(DecoratorPart));
decoratorStyle.Add(Border.BackgroundProperty, GlobalResourceKey.ColorBgContainer);
selectedStyle.Add(decoratorStyle);
}
commonStyle.Add(selectedStyle);
Add(commonStyle);
BuildSizeTypeStyle();
BuildPlacementStyle();
BuildDisabledStyle();
}
protected void BuildSizeTypeStyle()
{
var topOrBottomStyle = new Style(selector => Selectors.Or(selector.Nesting().PropertyEquals(TabItem.TabStripPlacementProperty, Dock.Top),
selector.Nesting().PropertyEquals(TabItem.TabStripPlacementProperty, Dock.Bottom)));
{
var largeSizeStyle = new Style(selector => selector.Nesting().PropertyEquals(TabItem.SizeTypeProperty, SizeType.Large));
{
var decoratorStyle = new Style(selector => selector.Nesting().Template().Name(DecoratorPart));
decoratorStyle.Add(Border.PaddingProperty, TabControlResourceKey.CardPaddingLG);
largeSizeStyle.Add(decoratorStyle);
}
topOrBottomStyle.Add(largeSizeStyle);
var middleSizeStyle = new Style(selector => selector.Nesting().PropertyEquals(TabItem.SizeTypeProperty, SizeType.Middle));
{
var decoratorStyle = new Style(selector => selector.Nesting().Template().Name(DecoratorPart));
decoratorStyle.Add(Border.PaddingProperty, TabControlResourceKey.CardPadding);
middleSizeStyle.Add(decoratorStyle);
}
topOrBottomStyle.Add(middleSizeStyle);
var smallSizeType = new Style(selector => selector.Nesting().PropertyEquals(TabItem.SizeTypeProperty, SizeType.Small));
{
var decoratorStyle = new Style(selector => selector.Nesting().Template().Name(DecoratorPart));
decoratorStyle.Add(Border.PaddingProperty, TabControlResourceKey.CardPaddingSM);
smallSizeType.Add(decoratorStyle);
}
topOrBottomStyle.Add(smallSizeType);
}
Add(topOrBottomStyle);
var leftOrRightStyle = new Style(selector => Selectors.Or(selector.Nesting().PropertyEquals(TabItem.TabStripPlacementProperty, Dock.Left),
selector.Nesting().PropertyEquals(TabItem.TabStripPlacementProperty, Dock.Right)));
{
var largeSizeStyle = new Style(selector => selector.Nesting().PropertyEquals(TabItem.SizeTypeProperty, SizeType.Large));
{
var decoratorStyle = new Style(selector => selector.Nesting().Template().Name(DecoratorPart));
decoratorStyle.Add(Border.PaddingProperty, TabControlResourceKey.VerticalItemPadding);
largeSizeStyle.Add(decoratorStyle);
}
leftOrRightStyle.Add(largeSizeStyle);
var middleSizeStyle = new Style(selector => selector.Nesting().PropertyEquals(TabItem.SizeTypeProperty, SizeType.Middle));
{
var decoratorStyle = new Style(selector => selector.Nesting().Template().Name(DecoratorPart));
decoratorStyle.Add(Border.PaddingProperty, TabControlResourceKey.VerticalItemPadding);
middleSizeStyle.Add(decoratorStyle);
}
leftOrRightStyle.Add(middleSizeStyle);
var smallSizeType = new Style(selector => selector.Nesting().PropertyEquals(TabItem.SizeTypeProperty, SizeType.Small));
{
var decoratorStyle = new Style(selector => selector.Nesting().Template().Name(DecoratorPart));
decoratorStyle.Add(Border.PaddingProperty, TabControlResourceKey.VerticalItemPadding);
smallSizeType.Add(decoratorStyle);
}
leftOrRightStyle.Add(smallSizeType);
}
Add(leftOrRightStyle);
}
private void BuildPlacementStyle()
{
// 设置 items presenter 面板样式
// 分为上、右、下、左
{
// 上
var topStyle = new Style(selector => selector.Nesting().PropertyEquals(TabItem.TabStripPlacementProperty, Dock.Top));
var iconStyle = new Style(selector => selector.Nesting().Template().OfType<PathIcon>());
iconStyle.Add(PathIcon.VerticalAlignmentProperty, VerticalAlignment.Center);
topStyle.Add(iconStyle);
var decoratorStyle = new Style(selector => selector.Nesting().Template().Name(DecoratorPart));
decoratorStyle.Add(Border.BorderBrushProperty, GlobalResourceKey.ColorBorderSecondary);
Add(topStyle);
}
{
// 右
var rightStyle = new Style(selector => selector.Nesting().PropertyEquals(TabItem.TabStripPlacementProperty, Dock.Right));
var iconStyle = new Style(selector => selector.Nesting().Template().OfType<PathIcon>());
iconStyle.Add(PathIcon.HorizontalAlignmentProperty, HorizontalAlignment.Center);
rightStyle.Add(iconStyle);
Add(rightStyle);
}
{
// 下
var bottomStyle = new Style(selector => selector.Nesting().PropertyEquals(TabItem.TabStripPlacementProperty, Dock.Bottom));
var iconStyle = new Style(selector => selector.Nesting().Template().OfType<PathIcon>());
iconStyle.Add(PathIcon.VerticalAlignmentProperty, VerticalAlignment.Center);
bottomStyle.Add(iconStyle);
Add(bottomStyle);
}
{
// 左
var leftStyle = new Style(selector => selector.Nesting().PropertyEquals(TabItem.TabStripPlacementProperty, Dock.Left));
var iconStyle = new Style(selector => selector.Nesting().Template().OfType<PathIcon>());
iconStyle.Add(PathIcon.HorizontalAlignmentProperty, HorizontalAlignment.Center);
leftStyle.Add(iconStyle);
Add(leftStyle);
}
}
private void BuildDisabledStyle()
{
var disabledStyle = new Style(selector => selector.Nesting().Class(StdPseudoClass.Disabled));
var decoratorStyle = new Style(selector => selector.Nesting().Template().Name(DecoratorPart));
decoratorStyle.Add(Border.BackgroundProperty, GlobalResourceKey.ColorBgContainerDisabled);
disabledStyle.Add(decoratorStyle);
Add(disabledStyle);
}
}

View File

@ -1,14 +0,0 @@
using Avalonia.Controls;
namespace AtomUI.Controls;
public class CardTabStrip : BaseTabStrip
{
protected override Control CreateContainerForItemOverride(object? item, int index, object? recycleKey)
{
return new TabStripItem()
{
Shape = TabSharp.Card
};
}
}

View File

@ -1,16 +0,0 @@
using AtomUI.Theme.Styling;
namespace AtomUI.Controls;
[ControlThemeProvider]
internal class CardTabStripItemTheme : BaseTabStripItemTheme
{
public const string ID = "CardTabStripItem";
public CardTabStripItemTheme() : base(typeof(TabStripItem)) { }
public override string ThemeResourceKey()
{
return ID;
}
}

View File

@ -1,15 +0,0 @@
using AtomUI.Theme.Styling;
using Avalonia.Controls;
namespace AtomUI.Controls;
[ControlThemeProvider]
internal class CardTabStripTheme : BaseTabStripTheme
{
public CardTabStripTheme() : base(typeof(CardTabStrip)) { }
protected override void NotifyBuildControlTemplate(BaseTabStrip baseTabStrip, INameScope scope, Border container)
{
}
}

View File

@ -1,6 +1,124 @@
namespace AtomUI.Controls;
using AtomUI.Theme.Styling;
using AtomUI.Theme.Utils;
using AtomUI.Utils;
using Avalonia;
using Avalonia.Animation;
using Avalonia.Animation.Easings;
using Avalonia.Controls;
using Avalonia.Controls.Presenters;
using Avalonia.Controls.Primitives;
using Avalonia.Media.Transformation;
public class TabControl
namespace AtomUI.Controls;
public class TabControl : BaseTabControl
{
#region
internal static readonly DirectProperty<TabControl, double> SelectedIndicatorThicknessProperty =
AvaloniaProperty.RegisterDirect<TabControl, double>(nameof(SelectedIndicatorThickness),
o => o.SelectedIndicatorThickness,
(o, v) => o.SelectedIndicatorThickness = v);
private double _selectedIndicatorThickness;
internal double SelectedIndicatorThickness
{
get => _selectedIndicatorThickness;
set => SetAndRaise(SelectedIndicatorThicknessProperty, ref _selectedIndicatorThickness, value);
}
#endregion
private Border? _selectedIndicator;
private ItemsPresenter? _itemsPresenter;
public TabControl()
{
SelectionChanged += HandleSelectionChanged;
LayoutUpdated += HandleLayoutUpdated;
}
private void HandleSelectionChanged(object? sender, SelectionChangedEventArgs args)
{
if (VisualRoot is not null) {
SetupSelectedIndicator();
}
}
private void HandleLayoutUpdated(object? sender, EventArgs args)
{
if (_selectedIndicator is not null) {
if (_selectedIndicator.Transitions is null) {
var transitions = new Transitions();
transitions.Add(AnimationUtils.CreateTransition<TransformOperationsTransition>(Border.RenderTransformProperty,
GlobalResourceKey.MotionDurationSlow, new ExponentialEaseOut()));
_selectedIndicator.Transitions = transitions;
// 只需要执行一次
LayoutUpdated -= HandleLayoutUpdated;
}
}
}
private void SetupSelectedIndicator()
{
if (_selectedIndicator is not null && SelectedItem is TabItem tabStripItem) {
var selectedBounds = tabStripItem.Bounds;
var builder = new TransformOperations.Builder(1);
var offset = _itemsPresenter?.Bounds.Position ?? default;
if (TabStripPlacement == Dock.Top) {
_selectedIndicator.SetValue(Border.WidthProperty, tabStripItem.DesiredSize.Width);
_selectedIndicator.SetValue(Border.HeightProperty, _selectedIndicatorThickness);
builder.AppendTranslate(offset.X + selectedBounds.Left, 0);
} else if (TabStripPlacement == Dock.Right) {
_selectedIndicator.SetValue(Border.HeightProperty, tabStripItem.DesiredSize.Height);
_selectedIndicator.SetValue(Border.WidthProperty, _selectedIndicatorThickness);
builder.AppendTranslate(0, offset.Y + selectedBounds.Y);
} else if (TabStripPlacement == Dock.Bottom) {
_selectedIndicator.SetValue(Border.WidthProperty, tabStripItem.DesiredSize.Width);
_selectedIndicator.SetValue(Border.HeightProperty, _selectedIndicatorThickness);
builder.AppendTranslate(offset.X + selectedBounds.Left, 0);
} else {
_selectedIndicator.SetValue(Border.HeightProperty, tabStripItem.DesiredSize.Height);
_selectedIndicator.SetValue(Border.WidthProperty, _selectedIndicatorThickness);
builder.AppendTranslate(0, offset.Y + selectedBounds.Y);
}
_selectedIndicator.RenderTransform = builder.Build();
}
}
protected override Size ArrangeOverride(Size finalSize)
{
var size = base.ArrangeOverride(finalSize);
if (SelectedItem is TabItem) {
SetupSelectedIndicator();
}
return size;
}
protected override Control CreateContainerForItemOverride(object? item, int index, object? recycleKey)
{
var tabItem = new TabItem
{
Shape = TabSharp.Line
};
return tabItem;
}
protected override void PrepareContainerForItemOverride(Control container, object? item, int index)
{
base.PrepareContainerForItemOverride(container, item, index);
if (container is TabItem tabItem) {
tabItem.Shape = TabSharp.Line;
}
}
protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
{
base.OnApplyTemplate(e);
_selectedIndicator = e.NameScope.Find<Border>(TabControlTheme.SelectedItemIndicatorPart);
_itemsPresenter = e.NameScope.Find<ItemsPresenter>(TabControlTheme.ItemsPresenterPart);
TokenResourceBinder.CreateGlobalResourceBinding(this, SelectedIndicatorThicknessProperty, GlobalResourceKey.LineWidthBold);
}
}

View File

@ -0,0 +1,22 @@
using Avalonia.Controls;
using Avalonia.Threading;
namespace AtomUI.Controls;
internal class TabControlOverflowMenuItem : BaseOverflowMenuItem
{
protected override Type StyleKeyOverride => typeof(BaseOverflowMenuItem);
public TabItem? TabItem { get; set; }
protected override void NotifyCloseRequest()
{
if (Parent is MenuBase menu) {
var eventArgs = new CloseTabRequestEventArgs(CloseTabEvent, TabItem!);
RaiseEvent(eventArgs);
Dispatcher.UIThread.Post(() =>
{
menu.Close();
});
}
}
}

View File

@ -0,0 +1,122 @@
using Avalonia.Controls;
using Avalonia.Controls.Primitives;
using Avalonia.Interactivity;
using Avalonia.Threading;
namespace AtomUI.Controls;
internal class TabControlScrollViewer : BaseTabScrollViewer
{
#region
internal BaseTabControl? TabControl { get; set; }
#endregion
protected override Type StyleKeyOverride => typeof(BaseTabScrollViewer);
protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
{
base.OnApplyTemplate(e);
if (_menuIndicator is not null) {
_menuIndicator.Click += HandleMenuIndicator;
}
}
private void HandleMenuIndicator(object? sender, RoutedEventArgs args)
{
if (_menuFlyout is null) {
_menuFlyout = new MenuFlyout();
}
if (TabStripPlacement == Dock.Top) {
_menuFlyout.Placement = PlacementMode.Bottom;
} else if (TabStripPlacement == Dock.Bottom) {
_menuFlyout.Placement = PlacementMode.Top;
} else if (TabStripPlacement == Dock.Right) {
_menuFlyout.Placement = PlacementMode.Left;
} else {
_menuFlyout.Placement = PlacementMode.Right;
}
// 收集没有完全显示的 Tab 列表
_menuFlyout.Items.Clear();
if (TabControl is not null) {
for (int i = 0; i < TabControl.ItemCount; i++) {
var itemContainer = TabControl.ContainerFromIndex(i)!;
if (itemContainer is TabItem tabItem) {
bool needAddToMenu = false;
var itemBounds = itemContainer.Bounds;
if (TabStripPlacement == Dock.Top || TabStripPlacement == Dock.Bottom) {
var left = Math.Floor(itemBounds.Left - Offset.X);
var right = Math.Floor(itemBounds.Right - Offset.X);
if (left < 0 || right > Viewport.Width) {
needAddToMenu = true;
}
} else {
var top = Math.Floor(itemBounds.Top - Offset.Y);
var bottom = Math.Floor(itemBounds.Bottom - Offset.Y);
if (top < 0 || bottom > Viewport.Height) {
needAddToMenu = true;
}
}
if (needAddToMenu) {
var menuItem = new TabControlOverflowMenuItem
{
Header = tabItem.Header,
TabItem = tabItem,
IsClosable = tabItem.IsClosable
};
menuItem.Click += HandleMenuItemClicked;
menuItem.CloseTab += HandleCloseTabRequest;
_menuFlyout.Items.Add(menuItem);
}
}
}
if (_menuFlyout.Items.Count > 0) {
_menuFlyout.ShowAt(_menuIndicator!);
}
}
}
private void HandleMenuItemClicked(object? sender, RoutedEventArgs args)
{
if (TabControl is not null) {
Dispatcher.UIThread.Post(sender =>
{
if (sender is TabControlOverflowMenuItem tabControlMenuItem) {
var tabItem = tabControlMenuItem.TabItem;
if (tabItem is not null) {
tabItem.BringIntoView();
TabControl.SelectedItem = tabItem;
}
}
}, sender);
}
}
private void HandleCloseTabRequest(object? sender, RoutedEventArgs args)
{
if (sender is TabControlOverflowMenuItem tabControlMenuItem) {
if (TabControl is not null) {
if (TabControl.SelectedItem is TabItem selectedItem) {
if (selectedItem == tabControlMenuItem.TabItem) {
var selectedIndex = TabControl.SelectedIndex;
object? newSelectedItem = null;
if (selectedIndex != 0) {
newSelectedItem = TabControl.Items[--selectedIndex];
}
TabControl.Items.Remove(tabControlMenuItem.TabItem);
TabControl.SelectedItem = newSelectedItem;
} else {
TabControl.Items.Remove(tabControlMenuItem.TabItem);
}
}
}
}
}
}

View File

@ -1,6 +1,141 @@
namespace AtomUI.Controls;
using AtomUI.Theme.Styling;
using AtomUI.Utils;
using Avalonia.Controls;
using Avalonia.Controls.Presenters;
using Avalonia.Controls.Templates;
using Avalonia.Layout;
using Avalonia.Styling;
public class TabControlTheme
namespace AtomUI.Controls;
[ControlThemeProvider]
internal class TabControlTheme : BaseTabControlTheme
{
public const string SelectedItemIndicatorPart = "PART_SelectedItemIndicator";
public TabControlTheme() : base(typeof(TabControl)) { }
protected override void NotifyBuildTabStripTemplate(BaseTabControl baseTabControl, INameScope scope, DockPanel container)
{
var alignWrapper = new Panel()
{
Name = AlignWrapperPart
};
alignWrapper.RegisterInNameScope(scope);
CreateTemplateParentBinding(alignWrapper, DockPanel.DockProperty, TabControl.TabStripPlacementProperty);
CreateTemplateParentBinding(alignWrapper, Panel.MarginProperty,TabControl.TabStripMarginProperty);
var tabScrollViewer = new TabControlScrollViewer()
{
Name = TabsContainerPart,
};
CreateTemplateParentBinding(tabScrollViewer, BaseTabScrollViewer.TabStripPlacementProperty, TabStrip.TabStripPlacementProperty);
var contentPanel = CreateTabStripContentPanel(scope);
tabScrollViewer.Content = contentPanel;
tabScrollViewer.TabControl = baseTabControl;
tabScrollViewer.RegisterInNameScope(scope);
alignWrapper.Children.Add(tabScrollViewer);
container.Children.Add(alignWrapper);
}
private Panel CreateTabStripContentPanel(INameScope scope)
{
var layout = new Panel();
var itemsPresenter = new ItemsPresenter
{
Name = ItemsPresenterPart,
};
itemsPresenter.RegisterInNameScope(scope);
var border = new Border
{
Name = SelectedItemIndicatorPart,
};
border.RegisterInNameScope(scope);
TokenResourceBinder.CreateTokenBinding(border, Border.BackgroundProperty, TabControlResourceKey.InkBarColor);
layout.Children.Add(itemsPresenter);
layout.Children.Add(border);
CreateTemplateParentBinding(itemsPresenter, ItemsPresenter.ItemsPanelProperty, TabControl.ItemsPanelProperty);
return layout;
}
protected override void BuildStyles()
{
base.BuildStyles();
var commonStyle = new Style(selector => selector.Nesting());
// 设置 items presenter 面板样式
// 分为上、右、下、左
{
// 上
var topStyle = new Style(selector => selector.Nesting().Class(BaseTabControl.TopPC));
var itemPresenterPanelStyle = new Style(selector => selector.Nesting().Template().Name(ItemsPresenterPart).Child().OfType<StackPanel>());
itemPresenterPanelStyle.Add(StackPanel.SpacingProperty, TabControlResourceKey.HorizontalItemGutter);
itemPresenterPanelStyle.Add(StackPanel.OrientationProperty, Orientation.Horizontal);
topStyle.Add(itemPresenterPanelStyle);
var indicatorStyle = new Style(selector => selector.Nesting().Template().Name(SelectedItemIndicatorPart));
indicatorStyle.Add(Border.HorizontalAlignmentProperty, HorizontalAlignment.Left);
indicatorStyle.Add(Border.VerticalAlignmentProperty, VerticalAlignment.Bottom);
topStyle.Add(indicatorStyle);
topStyle.Add(itemPresenterPanelStyle);
commonStyle.Add(topStyle);
}
{
// 右
var rightStyle = new Style(selector => selector.Nesting().Class(BaseTabControl.RightPC));
var itemPresenterPanelStyle = new Style(selector => selector.Nesting().Template().Name(ItemsPresenterPart).Child().OfType<StackPanel>());
itemPresenterPanelStyle.Add(StackPanel.OrientationProperty, Orientation.Vertical);
itemPresenterPanelStyle.Add(StackPanel.SpacingProperty, TabControlResourceKey.VerticalItemGutter);
rightStyle.Add(itemPresenterPanelStyle);
var indicatorStyle = new Style(selector => selector.Nesting().Template().Name(SelectedItemIndicatorPart));
indicatorStyle.Add(Border.HorizontalAlignmentProperty, HorizontalAlignment.Left);
indicatorStyle.Add(Border.VerticalAlignmentProperty, VerticalAlignment.Top);
rightStyle.Add(indicatorStyle);
commonStyle.Add(rightStyle);
}
{
// 下
var bottomStyle = new Style(selector => selector.Nesting().Class(BaseTabControl.BottomPC));
var itemPresenterPanelStyle = new Style(selector => selector.Nesting().Template().Name(ItemsPresenterPart).Child().OfType<StackPanel>());
itemPresenterPanelStyle.Add(StackPanel.SpacingProperty, TabControlResourceKey.HorizontalItemGutter);
itemPresenterPanelStyle.Add(StackPanel.OrientationProperty, Orientation.Horizontal);
bottomStyle.Add(itemPresenterPanelStyle);
var indicatorStyle = new Style(selector => selector.Nesting().Template().Name(SelectedItemIndicatorPart));
indicatorStyle.Add(Border.HorizontalAlignmentProperty, HorizontalAlignment.Left);
indicatorStyle.Add(Border.VerticalAlignmentProperty, VerticalAlignment.Top);
bottomStyle.Add(indicatorStyle);
commonStyle.Add(bottomStyle);
}
{
// 左
var leftStyle = new Style(selector => selector.Nesting().Class(BaseTabControl.LeftPC));
var itemPresenterPanelStyle = new Style(selector => selector.Nesting().Template().Name(ItemsPresenterPart).Child().OfType<StackPanel>());
itemPresenterPanelStyle.Add(StackPanel.OrientationProperty, Orientation.Vertical);
itemPresenterPanelStyle.Add(StackPanel.SpacingProperty, TabControlResourceKey.VerticalItemGutter);
leftStyle.Add(itemPresenterPanelStyle);
var indicatorStyle = new Style(selector => selector.Nesting().Template().Name(SelectedItemIndicatorPart));
indicatorStyle.Add(Border.HorizontalAlignmentProperty, HorizontalAlignment.Right);
indicatorStyle.Add(Border.VerticalAlignmentProperty, VerticalAlignment.Top);
leftStyle.Add(indicatorStyle);
commonStyle.Add(leftStyle);
}
Add(commonStyle);
}
}

View File

@ -18,9 +18,9 @@ internal class TabControlToken : AbstractControlDesignToken
public Color CardBg { get; set; }
/// <summary>
/// 卡片标签页高度
/// 卡片标签页大小
/// </summary>
public double CardHeight { get; set; }
public double CardSize { get; set; }
/// <summary>
/// 卡片标签页内间距
@ -87,16 +87,16 @@ internal class TabControlToken : AbstractControlDesignToken
/// </summary>
public Thickness HorizontalItemPaddingSM { get; set; }
/// <summary>
/// 纵向标签页标签间距
/// </summary>
public double VerticalItemGutter { get; set; }
/// <summary>
/// 纵向标签页标签内间距
/// </summary>
public Thickness VerticalItemPadding { get; set; }
/// <summary>
/// 纵向标签页标签外间距
/// </summary>
public Thickness VerticalItemMargin { get; set; }
/// <summary>
/// 标签文本颜色
/// </summary>
@ -116,7 +116,7 @@ internal class TabControlToken : AbstractControlDesignToken
/// 卡片标签间距
/// </summary>
public double CardGutter { get; set; }
/// <summary>
/// 标签内容 icon 的外边距
/// </summary>
@ -136,6 +136,26 @@ internal class TabControlToken : AbstractControlDesignToken
/// 滚动边缘的厚度
/// </summary>
public double MenuEdgeThickness { get; set; }
/// <summary>
/// 水平添加按钮外边距
/// </summary>
public Thickness AddTabButtonMarginHorizontal { get; set; }
/// <summary>
/// 垂直添加按钮外边距
/// </summary>
public Thickness AddTabButtonMarginVertical { get; set; }
/// <summary>
/// 关闭按钮外边距
/// </summary>
public Thickness CloseIconMargin { get; set; }
/// <summary>
/// Tab 标签和内容区域的间距
/// </summary>
public double TabAndContentGutter { get; set; }
internal override void CalculateFromAlias()
{
@ -146,8 +166,10 @@ internal class TabControlToken : AbstractControlDesignToken
var colorToken = _globalToken.ColorToken;
CardBg = _globalToken.ColorFillAlter;
CardHeight = _globalToken.HeightToken.ControlHeightLG;
CardPadding = new Thickness(_globalToken.Padding, (CardHeight - Math.Round(_globalToken.FontToken.FontSize * lineHeight)) / 2 - lineWidth);
CardSize = _globalToken.HeightToken.ControlHeightLG;
CardPadding = new Thickness(_globalToken.Padding, (CardSize - Math.Round(_globalToken.FontToken.FontSize * lineHeight)) / 2 - lineWidth);
CardPaddingSM = new Thickness(_globalToken.Padding, _globalToken.PaddingXXS * 1.5);
CardPaddingLG = new Thickness(top:_globalToken.PaddingXS,
bottom:_globalToken.PaddingXXS * 1.5,
@ -157,7 +179,9 @@ internal class TabControlToken : AbstractControlDesignToken
TitleFontSize = fontToken.FontSize;
TitleFontSizeLG = fontToken.FontSizeLG;
TitleFontSizeSM = fontToken.FontSize;
InkBarColor = colorToken.ColorPrimaryToken.ColorPrimary;
HorizontalMargin = new Thickness(0, 0, _globalToken.Margin, 0);
HorizontalItemGutter = 32;
HorizontalItemMargin = new Thickness();
@ -165,19 +189,24 @@ internal class TabControlToken : AbstractControlDesignToken
HorizontalItemPadding = new Thickness(0, _globalToken.PaddingSM);
HorizontalItemPaddingSM = new Thickness(0, _globalToken.PaddingXS);
HorizontalItemPaddingLG = new Thickness(0, _globalToken.Padding);
VerticalItemPadding = new Thickness(_globalToken.PaddingLG, _globalToken.PaddingXS);
VerticalItemMargin = new Thickness(0, _globalToken.Margin, 0, 0);
VerticalItemGutter = _globalToken.Margin;
VerticalItemPadding = new Thickness(_globalToken.PaddingXS, _globalToken.PaddingXS);
ItemColor = colorToken.ColorNeutralToken.ColorText;
ItemSelectedColor = colorToken.ColorPrimaryToken.ColorPrimary;
ItemHoverColor = colorToken.ColorPrimaryToken.ColorPrimaryHover;
CardGutter = _globalToken.MarginXXS / 2;
AddTabButtonMarginHorizontal = new Thickness(CardGutter, 0, 0, 0);
AddTabButtonMarginVertical = new Thickness(0, CardGutter, 0, 0);
ItemIconMargin = new Thickness(0, 0, _globalToken.MarginSM, 0);
MenuIndicatorPaddingHorizontal = new Thickness(_globalToken.PaddingXS, 0, 0, 0);
MenuIndicatorPaddingVertical = new Thickness(0, _globalToken.PaddingXS, 0, 0);
CloseIconMargin = new Thickness(_globalToken.MarginXXS, 0, 0, 0);
MenuEdgeThickness = 20;
TabAndContentGutter = _globalToken.MarginSM;
}
}

View File

@ -1,6 +1,209 @@
namespace AtomUI.Controls;
using AtomUI.Controls.Utils;
using AtomUI.Icon;
using AtomUI.Media;
using AtomUI.Theme.Styling;
using AtomUI.Theme.Utils;
using AtomUI.Utils;
using Avalonia;
using Avalonia.Animation;
using Avalonia.Controls;
using Avalonia.Controls.Primitives;
using Avalonia.Interactivity;
using Avalonia.Layout;
using Avalonia.LogicalTree;
using Avalonia.Rendering;
public class TabItem
namespace AtomUI.Controls;
using AvaloniaTabItem = Avalonia.Controls.TabItem;
public class TabItem : AvaloniaTabItem, IControlCustomStyle, ICustomHitTest
{
#region
public static readonly StyledProperty<SizeType> SizeTypeProperty =
BaseTabControl.SizeTypeProperty.AddOwner<TabItem>();
public static readonly StyledProperty<PathIcon?> IconProperty =
AvaloniaProperty.Register<TabItem, PathIcon?>(nameof(Icon));
public static readonly StyledProperty<PathIcon?> CloseIconProperty =
AvaloniaProperty.Register<TabItem, PathIcon?>(nameof(CloseIcon));
public static readonly StyledProperty<bool> IsClosableProperty =
AvaloniaProperty.Register<TabItem, bool>(nameof(IsClosable));
public SizeType SizeType
{
get => GetValue(SizeTypeProperty);
set => SetValue(SizeTypeProperty, value);
}
public PathIcon? Icon
{
get => GetValue(IconProperty);
set => SetValue(IconProperty, value);
}
public PathIcon? CloseIcon
{
get => GetValue(CloseIconProperty);
set => SetValue(CloseIconProperty, value);
}
public bool IsClosable
{
get => GetValue(IsClosableProperty);
set => SetValue(IsClosableProperty, value);
}
#endregion
#region
internal static readonly StyledProperty<TabSharp> ShapeProperty =
AvaloniaProperty.Register<TabItem, TabSharp>(nameof(Shape));
public TabSharp Shape
{
get => GetValue(ShapeProperty);
set => SetValue(ShapeProperty, value);
}
#endregion
private StackPanel? _contentLayout;
private IControlCustomStyle _customStyle;
private IconButton? _closeButton;
public TabItem()
{
_customStyle = this;
}
private void SetupItemIcon()
{
if (Icon is not null) {
UIStructureUtils.SetTemplateParent(Icon, this);
Icon.Name = BaseTabItemTheme.ItemIconPart;
if (Icon.ThemeType != IconThemeType.TwoTone) {
TokenResourceBinder.CreateTokenBinding(Icon, PathIcon.NormalFilledBrushProperty, TabControlResourceKey.ItemColor);
TokenResourceBinder.CreateTokenBinding(Icon, PathIcon.ActiveFilledBrushProperty, TabControlResourceKey.ItemHoverColor);
TokenResourceBinder.CreateTokenBinding(Icon, PathIcon.SelectedFilledBrushProperty, TabControlResourceKey.ItemSelectedColor);
TokenResourceBinder.CreateTokenBinding(Icon, PathIcon.DisabledFilledBrushProperty, GlobalResourceKey.ColorTextDisabled);
}
if (_contentLayout is not null) {
_contentLayout.Children.Insert(0, Icon);
}
}
}
private void SetupCloseIcon()
{
if (CloseIcon is null) {
CloseIcon = new PathIcon
{
Kind = "CloseOutlined"
};
TokenResourceBinder.CreateGlobalResourceBinding(CloseIcon, PathIcon.WidthProperty, GlobalResourceKey.IconSizeSM);
TokenResourceBinder.CreateGlobalResourceBinding(CloseIcon, PathIcon.HeightProperty, GlobalResourceKey.IconSizeSM);
}
CloseIcon.SetValue(PathIcon.VerticalAlignmentProperty, VerticalAlignment.Center);
UIStructureUtils.SetTemplateParent(CloseIcon, this);
if (CloseIcon.ThemeType != IconThemeType.TwoTone) {
TokenResourceBinder.CreateTokenBinding(CloseIcon, PathIcon.NormalFilledBrushProperty, GlobalResourceKey.ColorIcon);
TokenResourceBinder.CreateTokenBinding(CloseIcon, PathIcon.ActiveFilledBrushProperty, GlobalResourceKey.ColorIconHover);
TokenResourceBinder.CreateTokenBinding(CloseIcon, PathIcon.DisabledFilledBrushProperty, GlobalResourceKey.ColorTextDisabled);
}
}
protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
{
base.OnApplyTemplate(e);
_customStyle.HandleTemplateApplied(e.NameScope);
}
#region IControlCustomStyle
void IControlCustomStyle.HandleTemplateApplied(INameScope scope)
{
_contentLayout = scope.Find<StackPanel>(BaseTabItemTheme.ContentLayoutPart);
_closeButton = scope.Find<IconButton>(BaseTabItemTheme.ItemCloseButtonPart);
SetupItemIcon();
SetupCloseIcon();
if (Transitions is null) {
var transitions = new Transitions();
transitions.Add(AnimationUtils.CreateTransition<SolidColorBrushTransition>(ForegroundProperty));
Transitions = transitions;
}
if (_closeButton is not null) {
_closeButton.Click += HandleCloseRequest;
}
}
#endregion
private void HandleCloseRequest(object? sender, RoutedEventArgs args)
{
if (Parent is BaseTabControl tabControl) {
if (tabControl.SelectedItem is TabItem selectedItem) {
if (selectedItem == this) {
var selectedIndex = tabControl.SelectedIndex;
object? newSelectedItem = null;
if (selectedIndex != 0) {
newSelectedItem = tabControl.Items[--selectedIndex];
}
tabControl.Items.Remove(this);
tabControl.SelectedItem = newSelectedItem;
} else {
tabControl.Items.Remove(this);
}
}
}
}
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
{
base.OnPropertyChanged(change);
if (VisualRoot is not null) {
if (change.Property == IconProperty) {
var oldIcon = change.GetOldValue<PathIcon?>();
if (oldIcon != null) {
UIStructureUtils.SetTemplateParent(oldIcon, null);
}
SetupItemIcon();
}
}
if (change.Property == CloseIconProperty) {
var oldIcon = change.GetOldValue<PathIcon?>();
if (oldIcon != null) {
UIStructureUtils.SetTemplateParent(oldIcon, null);
}
SetupCloseIcon();
}
if (change.Property == ShapeProperty) {
HandleShapeChanged();
}
}
protected override void OnAttachedToLogicalTree(LogicalTreeAttachmentEventArgs e)
{
base.OnAttachedToLogicalTree(e);
HandleShapeChanged();
}
private void HandleShapeChanged()
{
if (Shape == TabSharp.Line) {
TokenResourceBinder.CreateTokenBinding(this, ThemeProperty, TabItemTheme.ID);
} else {
TokenResourceBinder.CreateTokenBinding(this, ThemeProperty, CardTabItemTheme.ID);
}
}
public bool HitTest(Point point)
{
return true;
}
}

View File

@ -1,6 +1,97 @@
namespace AtomUI.Controls;
using AtomUI.Theme.Styling;
using Avalonia.Controls;
using Avalonia.Styling;
public class TabItemTheme
namespace AtomUI.Controls;
[ControlThemeProvider]
internal class TabItemTheme : BaseTabItemTheme
{
public const string ID = "TabItem";
public TabItemTheme() : base(typeof(TabItem)) { }
public override string ThemeResourceKey()
{
return ID;
}
protected override void BuildStyles()
{
base.BuildStyles();
BuildSizeTypeStyle();
}
protected void BuildSizeTypeStyle()
{
var topOrBottomStyle = new Style(selector => Selectors.Or(selector.Nesting().PropertyEquals(TabItem.TabStripPlacementProperty, Dock.Top),
selector.Nesting().PropertyEquals(TabItem.TabStripPlacementProperty, Dock.Bottom)));
{
topOrBottomStyle.Add(Border.MarginProperty, TabControlResourceKey.HorizontalItemMargin);
var largeSizeStyle = new Style(selector => selector.Nesting().PropertyEquals(TabItem.SizeTypeProperty, SizeType.Large));
{
var decoratorStyle = new Style(selector => selector.Nesting().Template().Name(DecoratorPart));
decoratorStyle.Add(Border.PaddingProperty, TabControlResourceKey.HorizontalItemPaddingLG);
largeSizeStyle.Add(decoratorStyle);
}
topOrBottomStyle.Add(largeSizeStyle);
var middleSizeStyle = new Style(selector => selector.Nesting().PropertyEquals(TabItem.SizeTypeProperty, SizeType.Middle));
{
var decoratorStyle = new Style(selector => selector.Nesting().Template().Name(DecoratorPart));
decoratorStyle.Add(Border.PaddingProperty, TabControlResourceKey.HorizontalItemPadding);
middleSizeStyle.Add(decoratorStyle);
}
topOrBottomStyle.Add(middleSizeStyle);
var smallSizeType = new Style(selector => selector.Nesting().PropertyEquals(TabItem.SizeTypeProperty, SizeType.Small));
{
var decoratorStyle = new Style(selector => selector.Nesting().Template().Name(DecoratorPart));
decoratorStyle.Add(Border.PaddingProperty, TabControlResourceKey.HorizontalItemPaddingSM);
smallSizeType.Add(decoratorStyle);
}
topOrBottomStyle.Add(smallSizeType);
Add(topOrBottomStyle);
}
var leftOrRightStyle = new Style(selector => Selectors.Or(selector.Nesting().PropertyEquals(TabItem.TabStripPlacementProperty, Dock.Left),
selector.Nesting().PropertyEquals(TabItem.TabStripPlacementProperty, Dock.Right)));
{
// 貌似没必要分大小,但是先放着吧,万一需要难得再加
var largeSizeStyle = new Style(selector => selector.Nesting().PropertyEquals(TabItem.SizeTypeProperty, SizeType.Large));
{
var decoratorStyle = new Style(selector => selector.Nesting().Template().Name(DecoratorPart));
decoratorStyle.Add(Border.PaddingProperty, TabControlResourceKey.VerticalItemPadding);
largeSizeStyle.Add(decoratorStyle);
}
leftOrRightStyle.Add(largeSizeStyle);
var middleSizeStyle = new Style(selector => selector.Nesting().PropertyEquals(TabItem.SizeTypeProperty, SizeType.Middle));
{
var decoratorStyle = new Style(selector => selector.Nesting().Template().Name(DecoratorPart));
decoratorStyle.Add(Border.PaddingProperty, TabControlResourceKey.VerticalItemPadding);
middleSizeStyle.Add(decoratorStyle);
}
leftOrRightStyle.Add(middleSizeStyle);
var smallSizeType = new Style(selector => selector.Nesting().PropertyEquals(TabItem.SizeTypeProperty, SizeType.Small));
{
var decoratorStyle = new Style(selector => selector.Nesting().Template().Name(DecoratorPart));
decoratorStyle.Add(Border.PaddingProperty, TabControlResourceKey.VerticalItemPadding);
smallSizeType.Add(decoratorStyle);
}
leftOrRightStyle.Add(smallSizeType);
Add(leftOrRightStyle);
}
}
}

View File

@ -8,13 +8,13 @@ using Avalonia.Rendering;
namespace AtomUI.Controls;
internal class TabStripScrollContentPresenter : ScrollContentPresenter, ICustomHitTest
internal class TabScrollContentPresenter : ScrollContentPresenter, ICustomHitTest
{
internal Dock TabStripPlacement { get; set; } = Dock.Top;
private static readonly MethodInfo SnapOffsetMethodInfo;
static TabStripScrollContentPresenter()
static TabScrollContentPresenter()
{
SnapOffsetMethodInfo =
typeof(ScrollContentPresenter).GetMethod("SnapOffset", BindingFlags.Instance | BindingFlags.NonPublic)!;
@ -29,7 +29,6 @@ internal class TabStripScrollContentPresenter : ScrollContentPresenter, ICustomH
var x = Offset.X;
var y = Offset.Y;
var delta = e.Delta;
if (TabStripPlacement == Dock.Top || TabStripPlacement == Dock.Bottom) {
delta = new Vector(delta.Y, delta.X);
}

View File

@ -1,10 +1,12 @@
using AtomUI.Theme.Styling;
using AtomUI.Data;
using AtomUI.Theme.Data;
using AtomUI.Theme.Styling;
using AtomUI.Utils;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.Primitives;
using Avalonia.Controls.Templates;
using Avalonia.Layout;
using Avalonia.Data;
using Avalonia.LogicalTree;
using Avalonia.Media;
@ -18,13 +20,13 @@ public abstract class BaseTabStrip : AvaloniaTabStrip, ISizeTypeAware
public const string RightPC = ":right";
public const string BottomPC = ":bottom";
public const string LeftPC = ":left";
private static readonly FuncTemplate<Panel?> DefaultPanel =
new(() => new StackPanel { Orientation = Orientation.Horizontal});
new(() => new StackPanel());
#region
public static readonly StyledProperty<SizeType> SizeTypeProperty =
AvaloniaProperty.Register<TabStrip, SizeType>(nameof(SizeType), SizeType.Middle);
AvaloniaProperty.Register<BaseTabStrip, SizeType>(nameof(SizeType), SizeType.Middle);
public static readonly StyledProperty<Dock> TabStripPlacementProperty =
AvaloniaProperty.Register<BaseTabStrip, Dock>(nameof(TabStripPlacement), defaultValue: Dock.Top);
@ -52,7 +54,7 @@ public abstract class BaseTabStrip : AvaloniaTabStrip, ISizeTypeAware
#endregion
private Border? _mainContainer;
private Border? _frameDecorator;
static BaseTabStrip()
{
@ -63,14 +65,15 @@ public abstract class BaseTabStrip : AvaloniaTabStrip, ISizeTypeAware
protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
{
base.OnApplyTemplate(e);
_mainContainer = e.NameScope.Find<Border>(BaseTabStripTheme.MainContainerPart);
_frameDecorator = e.NameScope.Find<Border>(BaseTabStripTheme.FrameDecoratorPart);
SetupBorderBinding();
}
private void SetupBorderBinding()
{
if (_mainContainer is not null) {
TokenResourceBinder.CreateTokenBinding(this, BorderThicknessProperty, GlobalResourceKey.BorderThickness);
if (_frameDecorator is not null) {
TokenResourceBinder.CreateTokenBinding(this, BorderThicknessProperty, GlobalResourceKey.BorderThickness, BindingPriority.Template,
new RenderScaleAwareThicknessConfigure(this));
}
}
@ -79,6 +82,7 @@ public abstract class BaseTabStrip : AvaloniaTabStrip, ISizeTypeAware
base.PrepareContainerForItemOverride(container, item, index);
if (container is TabStripItem tabStripItem) {
tabStripItem.TabStripPlacement = TabStripPlacement;
BindUtils.RelayBind(this, SizeTypeProperty, tabStripItem, TabStripItem.SizeTypeProperty);
}
}
@ -92,8 +96,13 @@ public abstract class BaseTabStrip : AvaloniaTabStrip, ISizeTypeAware
{
base.OnPropertyChanged(change);
if (change.Property == TabStripPlacementProperty) {
RefreshContainers();
UpdatePseudoClasses();
for (var i = 0; i < ItemCount; ++i) {
var itemContainer = ContainerFromIndex(i);
if (itemContainer is TabStripItem tabStripItem) {
tabStripItem.TabStripPlacement = TabStripPlacement;
}
}
}
}

View File

@ -1,6 +1,7 @@
using AtomUI.Icon;
using AtomUI.Theme;
using AtomUI.Theme.Styling;
using AtomUI.Utils;
using Avalonia.Controls;
using Avalonia.Controls.Presenters;
using Avalonia.Controls.Templates;
@ -12,10 +13,11 @@ namespace AtomUI.Controls;
internal class BaseTabStripItemTheme : BaseControlTheme
{
public const string DecoratorPart = "Part_Decorator";
public const string ContentLayoutPart = "Part_ContentLayout";
public const string DecoratorPart = "PART_Decorator";
public const string ContentLayoutPart = "PART_ContentLayout";
public const string ContentPresenterPart = "PART_ContentPresenter";
public const string ItemIconPart = "PART_ItemIcon";
public const string ItemCloseButtonPart = "PART_ItemCloseButton";
public BaseTabStripItemTheme(Type targetType) : base(targetType) { }
@ -50,11 +52,18 @@ internal class BaseTabStripItemTheme : BaseControlTheme
containerLayout.Children.Add(contentPresenter);
CreateTemplateParentBinding(contentPresenter, ContentPresenter.ContentProperty, TabStripItem.ContentProperty);
CreateTemplateParentBinding(contentPresenter, ContentPresenter.ContentTemplateProperty, TabStripItem.ContentTemplateProperty);
var iconButton = new IconButton()
{
Name = ItemCloseButtonPart
};
iconButton.RegisterInNameScope(scope);
TokenResourceBinder.CreateTokenBinding(iconButton, IconButton.MarginProperty, TabControlResourceKey.CloseIconMargin);
var iconButton = new IconButton();
CreateTemplateParentBinding(iconButton, IconButton.IconProperty, TabStripItem.CloseIconProperty);
containerLayout.Children.Add(iconButton);
CreateTemplateParentBinding(iconButton, IconButton.IsVisibleProperty, TabStripItem.IsClosableProperty);
containerLayout.Children.Add(iconButton);
container.Child = containerLayout;
}
@ -64,6 +73,13 @@ internal class BaseTabStripItemTheme : BaseControlTheme
commonStyle.Add(TabStripItem.CursorProperty, new Cursor(StandardCursorType.Hand));
commonStyle.Add(TabStripItem.ForegroundProperty, TabControlResourceKey.ItemColor);
// Icon 一些通用属性
{
var iconStyle = new Style(selector => selector.Nesting().Template().Name(ItemIconPart));
iconStyle.Add(PathIcon.MarginProperty, TabControlResourceKey.ItemIconMargin);
commonStyle.Add(iconStyle);
}
// hover
var hoverStyle = new Style(selector => selector.Nesting().Class(StdPseudoClass.PointerOver));
hoverStyle.Add(TabStripItem.ForegroundProperty, TabControlResourceKey.ItemHoverColor);
@ -86,23 +102,91 @@ internal class BaseTabStripItemTheme : BaseControlTheme
commonStyle.Add(selectedStyle);
Add(commonStyle);
BuildSizeTypeStyle();
BuildPlacementStyle();
BuildDisabledStyle();
}
private void BuildSizeTypeStyle()
{
var largeSizeStyle = new Style(selector => selector.Nesting().PropertyEquals(TabStripItem.SizeTypeProperty, SizeType.Large));
largeSizeStyle.Add(TabStripItem.FontSizeProperty, TabControlResourceKey.TitleFontSizeLG);
{
// Icon
var iconStyle = new Style(selector => selector.Nesting().Template().Name(ItemIconPart));
iconStyle.Add(PathIcon.WidthProperty, GlobalResourceKey.IconSize);
iconStyle.Add(PathIcon.HeightProperty, GlobalResourceKey.IconSize);
largeSizeStyle.Add(iconStyle);
}
Add(largeSizeStyle);
var middleSizeStyle = new Style(selector => selector.Nesting().PropertyEquals(TabStripItem.SizeTypeProperty, SizeType.Middle));
{
// Icon
var iconStyle = new Style(selector => selector.Nesting().Template().Name(ItemIconPart));
iconStyle.Add(PathIcon.WidthProperty, GlobalResourceKey.IconSize);
iconStyle.Add(PathIcon.HeightProperty, GlobalResourceKey.IconSize);
middleSizeStyle.Add(iconStyle);
}
middleSizeStyle.Add(TabStripItem.FontSizeProperty, TabControlResourceKey.TitleFontSize);
Add(middleSizeStyle);
var smallSizeType = new Style(selector => selector.Nesting().PropertyEquals(TabStripItem.SizeTypeProperty, SizeType.Small));
{
// Icon
var iconStyle = new Style(selector => selector.Nesting().Template().Name(ItemIconPart));
iconStyle.Add(PathIcon.WidthProperty, GlobalResourceKey.IconSizeSM);
iconStyle.Add(PathIcon.HeightProperty, GlobalResourceKey.IconSizeSM);
smallSizeType.Add(iconStyle);
}
smallSizeType.Add(TabStripItem.FontSizeProperty, TabControlResourceKey.TitleFontSizeSM);
Add(smallSizeType);
}
private void BuildPlacementStyle()
{
// 设置 items presenter 面板样式
// 分为上、右、下、左
{
// 上
var topStyle = new Style(selector => selector.Nesting().PropertyEquals(TabStripItem.TabStripPlacementProperty, Dock.Top));
var iconStyle = new Style(selector => selector.Nesting().Template().OfType<PathIcon>());
iconStyle.Add(PathIcon.VerticalAlignmentProperty, VerticalAlignment.Center);
topStyle.Add(iconStyle);
Add(topStyle);
}
{
// 右
var rightStyle = new Style(selector => selector.Nesting().PropertyEquals(TabStripItem.TabStripPlacementProperty, Dock.Right));
var iconStyle = new Style(selector => selector.Nesting().Template().OfType<PathIcon>());
iconStyle.Add(PathIcon.HorizontalAlignmentProperty, HorizontalAlignment.Left);
iconStyle.Add(PathIcon.VerticalAlignmentProperty, VerticalAlignment.Center);
rightStyle.Add(iconStyle);
Add(rightStyle);
}
{
// 下
var bottomStyle = new Style(selector => selector.Nesting().PropertyEquals(TabStripItem.TabStripPlacementProperty, Dock.Bottom));
var iconStyle = new Style(selector => selector.Nesting().Template().OfType<PathIcon>());
iconStyle.Add(PathIcon.VerticalAlignmentProperty, VerticalAlignment.Center);
bottomStyle.Add(iconStyle);
Add(bottomStyle);
}
{
// 左
var leftStyle = new Style(selector => selector.Nesting().PropertyEquals(TabStripItem.TabStripPlacementProperty, Dock.Left));
var iconStyle = new Style(selector => selector.Nesting().Template().OfType<PathIcon>());
iconStyle.Add(PathIcon.VerticalAlignmentProperty, VerticalAlignment.Center);
iconStyle.Add(PathIcon.HorizontalAlignmentProperty, HorizontalAlignment.Left);
leftStyle.Add(iconStyle);
Add(leftStyle);
}
}
private void BuildDisabledStyle()
{

View File

@ -0,0 +1,121 @@
using AtomUI.Theme;
using AtomUI.Theme.Styling;
using Avalonia.Controls;
using Avalonia.Controls.Presenters;
using Avalonia.Controls.Templates;
using Avalonia.Layout;
using Avalonia.Styling;
namespace AtomUI.Controls;
internal class BaseTabStripTheme : BaseControlTheme
{
public const string FrameDecoratorPart = "PART_FrameDecorator";
public const string ItemsPresenterPart = "PART_ItemsPresenter";
public const string TabsContainerPart = "PART_TabsContainer";
public const string AlignWrapperPart = "PART_AlignWrapper";
public BaseTabStripTheme(Type targetType) : base(targetType) { }
protected override IControlTemplate BuildControlTemplate()
{
return new FuncControlTemplate<BaseTabStrip>((strip, scope) =>
{
var frameDecorator = new Border()
{
Name = FrameDecoratorPart
};
frameDecorator.RegisterInNameScope(scope);
NotifyBuildControlTemplate(strip, scope, frameDecorator);
return frameDecorator;
});
}
protected virtual void NotifyBuildControlTemplate(BaseTabStrip baseTabStrip, INameScope scope, Border container)
{
}
protected override void BuildStyles()
{
base.BuildStyles();
var commonStyle = new Style(selector => selector.Nesting());
commonStyle.Add(BaseTabStrip.BorderBrushProperty, GlobalResourceKey.ColorBorderSecondary);
// 设置 items presenter 是否居中
// 分为上、右、下、左
{
// 上
var topStyle = new Style(selector => selector.Nesting().Class(BaseTabStrip.TopPC));
topStyle.Add(BaseTabStrip.HorizontalAlignmentProperty, HorizontalAlignment.Stretch);
topStyle.Add(BaseTabStrip.VerticalAlignmentProperty, VerticalAlignment.Top);
// tabs 是否居中
var tabAlignCenterStyle = new Style(selector => selector.Nesting().PropertyEquals(TabStrip.TabAlignmentCenterProperty, true));
{
var tabsContainerStyle = new Style(selector => selector.Nesting().Template().Name(TabsContainerPart));
tabsContainerStyle.Add(ItemsPresenter.HorizontalAlignmentProperty, HorizontalAlignment.Center);
tabAlignCenterStyle.Add(tabsContainerStyle);
}
topStyle.Add(tabAlignCenterStyle);
commonStyle.Add(topStyle);
}
{
// 右
var rightStyle = new Style(selector => selector.Nesting().Class(BaseTabStrip.RightPC));
rightStyle.Add(BaseTabStrip.HorizontalAlignmentProperty, HorizontalAlignment.Left);
rightStyle.Add(BaseTabStrip.VerticalAlignmentProperty, VerticalAlignment.Stretch);
// tabs 是否居中
var tabAlignCenterStyle = new Style(selector => selector.Nesting().PropertyEquals(TabStrip.TabAlignmentCenterProperty, true));
{
var tabsContainerStyle = new Style(selector => selector.Nesting().Template().Name(TabsContainerPart));
tabsContainerStyle.Add(ItemsPresenter.VerticalAlignmentProperty, VerticalAlignment.Center);
tabAlignCenterStyle.Add(tabsContainerStyle);
}
rightStyle.Add(tabAlignCenterStyle);
commonStyle.Add(rightStyle);
}
{
// 下
var bottomStyle = new Style(selector => selector.Nesting().Class(BaseTabStrip.BottomPC));
bottomStyle.Add(BaseTabStrip.HorizontalAlignmentProperty, HorizontalAlignment.Stretch);
bottomStyle.Add(BaseTabStrip.VerticalAlignmentProperty, VerticalAlignment.Top);
// tabs 是否居中
var tabAlignCenterStyle = new Style(selector => selector.Nesting().PropertyEquals(TabStrip.TabAlignmentCenterProperty, true));
{
var tabsContainerStyle = new Style(selector => selector.Nesting().Template().Name(TabsContainerPart));
tabsContainerStyle.Add(ItemsPresenter.HorizontalAlignmentProperty, HorizontalAlignment.Center);
tabAlignCenterStyle.Add(tabsContainerStyle);
}
bottomStyle.Add(tabAlignCenterStyle);
commonStyle.Add(bottomStyle);
}
{
// 左
var leftStyle = new Style(selector => selector.Nesting().Class(BaseTabStrip.LeftPC));
leftStyle.Add(BaseTabStrip.HorizontalAlignmentProperty, HorizontalAlignment.Left);
leftStyle.Add(BaseTabStrip.VerticalAlignmentProperty, VerticalAlignment.Stretch);
// tabs 是否居中
var tabAlignCenterStyle = new Style(selector => selector.Nesting().PropertyEquals(TabStrip.TabAlignmentCenterProperty, true));
{
var tabsContainerStyle = new Style(selector => selector.Nesting().Template().Name(TabsContainerPart));
tabsContainerStyle.Add(ItemsPresenter.VerticalAlignmentProperty, VerticalAlignment.Center);
tabAlignCenterStyle.Add(tabsContainerStyle);
}
leftStyle.Add(tabAlignCenterStyle);
commonStyle.Add(leftStyle);
}
Add(commonStyle);
}
}

View File

@ -0,0 +1,202 @@
using AtomUI.Data;
using AtomUI.Theme.Data;
using AtomUI.Theme.Styling;
using AtomUI.Utils;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.Presenters;
using Avalonia.Controls.Primitives;
using Avalonia.Data;
using Avalonia.Interactivity;
using Avalonia.Layout;
namespace AtomUI.Controls;
public class CardTabStrip : BaseTabStrip, IControlCustomStyle
{
#region
public readonly static StyledProperty<bool> IsShowAddTabButtonProperty =
AvaloniaProperty.Register<CardTabStrip, bool>(nameof(IsShowAddTabButton), false);
public readonly static RoutedEvent<RoutedEventArgs> AddTabRequestEvent =
RoutedEvent.Register<CardTabStrip, RoutedEventArgs>(
nameof(AddTabRequest),
RoutingStrategies.Bubble);
public bool IsShowAddTabButton
{
get => GetValue(IsShowAddTabButtonProperty);
set => SetValue(IsShowAddTabButtonProperty, value);
}
public event EventHandler<RoutedEventArgs>? AddTabRequest
{
add => AddHandler(AddTabRequestEvent, value);
remove => RemoveHandler(AddTabRequestEvent, value);
}
#endregion
#region
internal static readonly StyledProperty<CornerRadius> CardBorderRadiusProperty =
AvaloniaProperty.Register<CardTabStrip, CornerRadius>(nameof(CardBorderRadius));
internal static readonly StyledProperty<Thickness> CardBorderThicknessProperty =
AvaloniaProperty.Register<CardTabStrip, Thickness>(nameof(CardBorderThickness));
internal static readonly DirectProperty<CardTabStrip, CornerRadius> CardBorderRadiusSizeProperty =
AvaloniaProperty.RegisterDirect<CardTabStrip, CornerRadius>(nameof(CardBorderRadiusSize),
o => o.CardBorderRadiusSize,
(o, v) => o.CardBorderRadiusSize = v);
internal static readonly DirectProperty<CardTabStrip, double> CardSizeProperty =
AvaloniaProperty.RegisterDirect<CardTabStrip, double>(nameof(CardSize),
o => o.CardSize,
(o, v) => o.CardSize = v);
internal CornerRadius CardBorderRadius
{
get => GetValue(CardBorderRadiusProperty);
set => SetValue(CardBorderRadiusProperty, value);
}
internal Thickness CardBorderThickness
{
get => GetValue(CardBorderThicknessProperty);
set => SetValue(CardBorderThicknessProperty, value);
}
private CornerRadius _cardBorderRadiusSize;
internal CornerRadius CardBorderRadiusSize
{
get => _cardBorderRadiusSize;
set => SetAndRaise(CardBorderRadiusSizeProperty, ref _cardBorderRadiusSize, value);
}
private double _cardSize;
internal double CardSize
{
get => _cardSize;
set => SetAndRaise(CardSizeProperty, ref _cardSize, value);
}
#endregion
private IControlCustomStyle _customStyle;
private IconButton? _addTabButton;
private ItemsPresenter? _itemsPresenter;
private Grid? _cardTabStripContainer;
private BaseTabScrollViewer? _tabScrollViewer;
public CardTabStrip()
{
_customStyle = this;
}
protected override Control CreateContainerForItemOverride(object? item, int index, object? recycleKey)
{
return new TabStripItem
{
Shape = TabSharp.Card
};
}
protected override void PrepareContainerForItemOverride(Control container, object? item, int index)
{
base.PrepareContainerForItemOverride(container, item, index);
if (container is TabStripItem tabStripItem) {
tabStripItem.Shape = TabSharp.Card;
BindUtils.RelayBind(this, CardBorderRadiusProperty, tabStripItem, TabStripItem.CornerRadiusProperty);
BindUtils.RelayBind(this, CardBorderThicknessProperty, tabStripItem, TabStripItem.BorderThicknessProperty);
}
}
protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
{
base.OnApplyTemplate(e);
TokenResourceBinder.CreateTokenBinding(this, CardBorderThicknessProperty, GlobalResourceKey.BorderThickness, BindingPriority.Template,
new RenderScaleAwareThicknessConfigure(this));
TokenResourceBinder.CreateTokenBinding(this, CardSizeProperty, TabControlResourceKey.CardSize);
_customStyle.HandleTemplateApplied(e.NameScope);
}
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
{
base.OnPropertyChanged(change);
if (change.Property == SizeTypeProperty) {
HandleSizeTypeChanged();
} else if (change.Property == TabStripPlacementProperty) {
HandleTabStripPlacementChanged();
}
}
#region IControlCustomStyle
void IControlCustomStyle.HandleTemplateApplied(INameScope scope)
{
_addTabButton = scope.Find<IconButton>(CardTabStripTheme.AddTabButtonPart);
_itemsPresenter = scope.Find<ItemsPresenter>(CardTabStripTheme.ItemsPresenterPart);
_cardTabStripContainer = scope.Find<Grid>(CardTabStripTheme.CardTabStripContainerPart);
_tabScrollViewer = scope.Find<BaseTabScrollViewer>(CardTabStripTheme.CardTabStripScrollViewerPart);
if (_addTabButton is not null) {
_addTabButton.Click += HandleAddButtonClicked;
}
HandleSizeTypeChanged();
}
#endregion
private void HandleAddButtonClicked(object? sender, RoutedEventArgs args)
{
RaiseEvent(new RoutedEventArgs(AddTabRequestEvent));
}
private void HandleSizeTypeChanged()
{
if (SizeType == SizeType.Large) {
TokenResourceBinder.CreateGlobalResourceBinding(this, CardBorderRadiusSizeProperty, GlobalResourceKey.BorderRadiusLG);
} else if (SizeType == SizeType.Middle) {
TokenResourceBinder.CreateGlobalResourceBinding(this, CardBorderRadiusSizeProperty, GlobalResourceKey.BorderRadius);
} else {
TokenResourceBinder.CreateGlobalResourceBinding(this, CardBorderRadiusSizeProperty, GlobalResourceKey.BorderRadiusSM);
}
}
protected override Size ArrangeOverride(Size finalSize)
{
var size = base.ArrangeOverride(finalSize);
HandleTabStripPlacementChanged();
return size;
}
private void HandleTabStripPlacementChanged()
{
if (TabStripPlacement == Dock.Top) {
CardBorderRadius = new CornerRadius(topLeft: _cardBorderRadiusSize.TopLeft, topRight:_cardBorderRadiusSize.TopRight, bottomLeft:0, bottomRight:0);
if (_addTabButton is not null && _itemsPresenter is not null) {
_addTabButton.Width = _cardSize;
_addTabButton.Height = _itemsPresenter.DesiredSize.Height;
}
} else if (TabStripPlacement == Dock.Bottom) {
CardBorderRadius = new CornerRadius(topLeft: 0, 0, bottomLeft:_cardBorderRadiusSize.BottomLeft, bottomRight:_cardBorderRadiusSize.BottomRight);
if (_addTabButton is not null && _itemsPresenter is not null) {
_addTabButton.Width = _cardSize;
_addTabButton.Height = _itemsPresenter.DesiredSize.Height;
}
} else if (TabStripPlacement == Dock.Left) {
CardBorderRadius = new CornerRadius(topLeft: _cardBorderRadiusSize.TopLeft, 0, bottomLeft:_cardBorderRadiusSize.BottomLeft, bottomRight:0);
if (_addTabButton is not null && _itemsPresenter is not null) {
_addTabButton.Width = _cardSize;
_addTabButton.Height = _cardSize;
_addTabButton.HorizontalAlignment = HorizontalAlignment.Right;
}
} else {
CardBorderRadius = new CornerRadius(topLeft: 0, topRight:_cardBorderRadiusSize.TopRight, bottomLeft:0, bottomRight:_cardBorderRadiusSize.BottomRight);
if (_addTabButton is not null && _itemsPresenter is not null) {
_addTabButton.Width = _cardSize;
_addTabButton.Height = _cardSize;
_addTabButton.HorizontalAlignment = HorizontalAlignment.Left;
}
}
}
}

View File

@ -0,0 +1,181 @@
using AtomUI.Media;
using AtomUI.Theme.Styling;
using AtomUI.Theme.Utils;
using Avalonia.Animation;
using Avalonia.Controls;
using Avalonia.Layout;
using Avalonia.Styling;
namespace AtomUI.Controls;
[ControlThemeProvider]
internal class CardTabStripItemTheme : BaseTabStripItemTheme
{
public const string ID = "CardTabStripItem";
public CardTabStripItemTheme() : base(typeof(TabStripItem)) { }
public override string ThemeResourceKey()
{
return ID;
}
protected override void NotifyBuildControlTemplate(TabStripItem stripItem, INameScope scope, Border container)
{
base.NotifyBuildControlTemplate(stripItem, scope, container);
if (container.Transitions is null) {
var transitions = new Transitions();
transitions.Add(AnimationUtils.CreateTransition<SolidColorBrushTransition>(Border.BackgroundProperty));
container.Transitions = transitions;
}
CreateTemplateParentBinding(container, Border.BorderThicknessProperty, TabStripItem.BorderThicknessProperty);
CreateTemplateParentBinding(container, Border.CornerRadiusProperty, TabStripItem.CornerRadiusProperty);
}
protected override void BuildStyles()
{
base.BuildStyles();
var commonStyle = new Style(selector => selector.Nesting());
{
var decoratorStyle = new Style(selector => selector.Nesting().Template().Name(DecoratorPart));
decoratorStyle.Add(Border.MarginProperty, TabControlResourceKey.HorizontalItemMargin);
decoratorStyle.Add(Border.BackgroundProperty, TabControlResourceKey.CardBg);
decoratorStyle.Add(Border.BorderBrushProperty, GlobalResourceKey.ColorBorderSecondary);
commonStyle.Add(decoratorStyle);
}
// 选中
var selectedStyle = new Style(selector => selector.Nesting().Class(StdPseudoClass.Selected));
{
var decoratorStyle = new Style(selector => selector.Nesting().Template().Name(DecoratorPart));
decoratorStyle.Add(Border.BackgroundProperty, GlobalResourceKey.ColorBgContainer);
selectedStyle.Add(decoratorStyle);
}
commonStyle.Add(selectedStyle);
Add(commonStyle);
BuildSizeTypeStyle();
BuildPlacementStyle();
BuildDisabledStyle();
}
protected void BuildSizeTypeStyle()
{
var topOrBottomStyle = new Style(selector => Selectors.Or(selector.Nesting().PropertyEquals(TabStripItem.TabStripPlacementProperty, Dock.Top),
selector.Nesting().PropertyEquals(TabStripItem.TabStripPlacementProperty, Dock.Bottom)));
{
var largeSizeStyle = new Style(selector => selector.Nesting().PropertyEquals(TabStripItem.SizeTypeProperty, SizeType.Large));
{
var decoratorStyle = new Style(selector => selector.Nesting().Template().Name(DecoratorPart));
decoratorStyle.Add(Border.PaddingProperty, TabControlResourceKey.CardPaddingLG);
largeSizeStyle.Add(decoratorStyle);
}
topOrBottomStyle.Add(largeSizeStyle);
var middleSizeStyle = new Style(selector => selector.Nesting().PropertyEquals(TabStripItem.SizeTypeProperty, SizeType.Middle));
{
var decoratorStyle = new Style(selector => selector.Nesting().Template().Name(DecoratorPart));
decoratorStyle.Add(Border.PaddingProperty, TabControlResourceKey.CardPadding);
middleSizeStyle.Add(decoratorStyle);
}
topOrBottomStyle.Add(middleSizeStyle);
var smallSizeType = new Style(selector => selector.Nesting().PropertyEquals(TabStripItem.SizeTypeProperty, SizeType.Small));
{
var decoratorStyle = new Style(selector => selector.Nesting().Template().Name(DecoratorPart));
decoratorStyle.Add(Border.PaddingProperty, TabControlResourceKey.CardPaddingSM);
smallSizeType.Add(decoratorStyle);
}
topOrBottomStyle.Add(smallSizeType);
}
Add(topOrBottomStyle);
var leftOrRightStyle = new Style(selector => Selectors.Or(selector.Nesting().PropertyEquals(TabStripItem.TabStripPlacementProperty, Dock.Left),
selector.Nesting().PropertyEquals(TabStripItem.TabStripPlacementProperty, Dock.Right)));
{
var largeSizeStyle = new Style(selector => selector.Nesting().PropertyEquals(TabStripItem.SizeTypeProperty, SizeType.Large));
{
var decoratorStyle = new Style(selector => selector.Nesting().Template().Name(DecoratorPart));
decoratorStyle.Add(Border.PaddingProperty, TabControlResourceKey.VerticalItemPadding);
largeSizeStyle.Add(decoratorStyle);
}
leftOrRightStyle.Add(largeSizeStyle);
var middleSizeStyle = new Style(selector => selector.Nesting().PropertyEquals(TabStripItem.SizeTypeProperty, SizeType.Middle));
{
var decoratorStyle = new Style(selector => selector.Nesting().Template().Name(DecoratorPart));
decoratorStyle.Add(Border.PaddingProperty, TabControlResourceKey.VerticalItemPadding);
middleSizeStyle.Add(decoratorStyle);
}
leftOrRightStyle.Add(middleSizeStyle);
var smallSizeType = new Style(selector => selector.Nesting().PropertyEquals(TabStripItem.SizeTypeProperty, SizeType.Small));
{
var decoratorStyle = new Style(selector => selector.Nesting().Template().Name(DecoratorPart));
decoratorStyle.Add(Border.PaddingProperty, TabControlResourceKey.VerticalItemPadding);
smallSizeType.Add(decoratorStyle);
}
leftOrRightStyle.Add(smallSizeType);
}
Add(leftOrRightStyle);
}
private void BuildPlacementStyle()
{
// 设置 items presenter 面板样式
// 分为上、右、下、左
{
// 上
var topStyle = new Style(selector => selector.Nesting().PropertyEquals(TabStripItem.TabStripPlacementProperty, Dock.Top));
var iconStyle = new Style(selector => selector.Nesting().Template().OfType<PathIcon>());
iconStyle.Add(PathIcon.VerticalAlignmentProperty, VerticalAlignment.Center);
topStyle.Add(iconStyle);
var decoratorStyle = new Style(selector => selector.Nesting().Template().Name(DecoratorPart));
decoratorStyle.Add(Border.BorderBrushProperty, GlobalResourceKey.ColorBorderSecondary);
Add(topStyle);
}
{
// 右
var rightStyle = new Style(selector => selector.Nesting().PropertyEquals(TabStripItem.TabStripPlacementProperty, Dock.Right));
var iconStyle = new Style(selector => selector.Nesting().Template().OfType<PathIcon>());
iconStyle.Add(PathIcon.HorizontalAlignmentProperty, HorizontalAlignment.Center);
rightStyle.Add(iconStyle);
Add(rightStyle);
}
{
// 下
var bottomStyle = new Style(selector => selector.Nesting().PropertyEquals(TabStripItem.TabStripPlacementProperty, Dock.Bottom));
var iconStyle = new Style(selector => selector.Nesting().Template().OfType<PathIcon>());
iconStyle.Add(PathIcon.VerticalAlignmentProperty, VerticalAlignment.Center);
bottomStyle.Add(iconStyle);
Add(bottomStyle);
}
{
// 左
var leftStyle = new Style(selector => selector.Nesting().PropertyEquals(TabStripItem.TabStripPlacementProperty, Dock.Left));
var iconStyle = new Style(selector => selector.Nesting().Template().OfType<PathIcon>());
iconStyle.Add(PathIcon.HorizontalAlignmentProperty, HorizontalAlignment.Center);
leftStyle.Add(iconStyle);
Add(leftStyle);
}
}
private void BuildDisabledStyle()
{
var disabledStyle = new Style(selector => selector.Nesting().Class(StdPseudoClass.Disabled));
var decoratorStyle = new Style(selector => selector.Nesting().Template().Name(DecoratorPart));
decoratorStyle.Add(Border.BackgroundProperty, GlobalResourceKey.ColorBgContainerDisabled);
disabledStyle.Add(decoratorStyle);
Add(disabledStyle);
}
}

View File

@ -0,0 +1,176 @@
using AtomUI.Theme.Styling;
using AtomUI.Utils;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.Presenters;
using Avalonia.Controls.Templates;
using Avalonia.Layout;
using Avalonia.Styling;
namespace AtomUI.Controls;
[ControlThemeProvider]
internal class CardTabStripTheme : BaseTabStripTheme
{
public const string AddTabButtonPart = "PART_AddTabButton";
public const string CardTabStripContainerPart = "PART_CardTabStripContainer";
public const string CardTabStripScrollViewerPart = "PART_CardTabStripScrollViewer";
public CardTabStripTheme() : base(typeof(CardTabStrip)) { }
protected override void NotifyBuildControlTemplate(BaseTabStrip baseTabStrip, INameScope scope, Border container)
{
var alignWrapper = new Panel()
{
Name = AlignWrapperPart
};
var cardTabStripContainer = new TabsContainerPanel()
{
Name = TabsContainerPart,
};
cardTabStripContainer.RegisterInNameScope(scope);
CreateTemplateParentBinding(cardTabStripContainer, TabsContainerPanel.TabStripPlacementProperty, TabStrip.TabStripPlacementProperty);
var tabScrollViewer = new TabStripScrollViewer()
{
Name = CardTabStripScrollViewerPart
};
tabScrollViewer.RegisterInNameScope(scope);
CreateTemplateParentBinding(tabScrollViewer, BaseTabScrollViewer.TabStripPlacementProperty, TabStrip.TabStripPlacementProperty);
var contentPanel = CreateTabStripContentPanel(scope);
tabScrollViewer.Content = contentPanel;
tabScrollViewer.TabStrip = baseTabStrip;
var addTabIcon = new PathIcon()
{
Kind = "PlusOutlined"
};
TokenResourceBinder.CreateTokenBinding(addTabIcon, PathIcon.NormalFilledBrushProperty, TabControlResourceKey.ItemColor);
TokenResourceBinder.CreateTokenBinding(addTabIcon, PathIcon.ActiveFilledBrushProperty, TabControlResourceKey.ItemHoverColor);
TokenResourceBinder.CreateTokenBinding(addTabIcon, PathIcon.DisabledFilledBrushProperty, GlobalResourceKey.ColorTextDisabled);
TokenResourceBinder.CreateGlobalResourceBinding(addTabIcon, PathIcon.WidthProperty, GlobalResourceKey.IconSize);
TokenResourceBinder.CreateGlobalResourceBinding(addTabIcon, PathIcon.HeightProperty, GlobalResourceKey.IconSize);
var addTabButton = new IconButton
{
Name = AddTabButtonPart,
BorderThickness = new Thickness(1),
Icon = addTabIcon
};
CreateTemplateParentBinding(addTabButton, IconButton.BorderThicknessProperty, CardTabStrip.CardBorderThicknessProperty);
CreateTemplateParentBinding(addTabButton, IconButton.CornerRadiusProperty, CardTabStrip.CardBorderRadiusProperty);
CreateTemplateParentBinding(addTabButton, IconButton.IsVisibleProperty, CardTabStrip.IsShowAddTabButtonProperty);
TokenResourceBinder.CreateGlobalResourceBinding(addTabButton, IconButton.BorderBrushProperty, GlobalResourceKey.ColorBorderSecondary);
addTabButton.RegisterInNameScope(scope);
cardTabStripContainer.TabScrollViewer = tabScrollViewer;
cardTabStripContainer.AddTabButton = addTabButton;
alignWrapper.Children.Add(cardTabStripContainer);
container.Child = alignWrapper;
}
private ItemsPresenter CreateTabStripContentPanel(INameScope scope)
{
var itemsPresenter = new ItemsPresenter
{
Name = ItemsPresenterPart,
};
itemsPresenter.RegisterInNameScope(scope);
CreateTemplateParentBinding(itemsPresenter, ItemsPresenter.ItemsPanelProperty, TabStrip.ItemsPanelProperty);
return itemsPresenter;
}
protected override void BuildStyles()
{
base.BuildStyles();
var commonStyle = new Style(selector => selector.Nesting());
// 设置 items presenter 面板样式
// 分为上、右、下、左
{
// 上
var topStyle = new Style(selector => selector.Nesting().Class(BaseTabStrip.TopPC));
var containerStyle = new Style(selector => selector.Nesting().Template().Name(CardTabStripContainerPart));
topStyle.Add(containerStyle);
var itemPresenterPanelStyle = new Style(selector => selector.Nesting().Template().Name(ItemsPresenterPart).Child().OfType<StackPanel>());
itemPresenterPanelStyle.Add(StackPanel.OrientationProperty, Orientation.Horizontal);
itemPresenterPanelStyle.Add(StackPanel.SpacingProperty, TabControlResourceKey.CardGutter);
var addTabButtonStyle = new Style(selector => selector.Nesting().Template().Name(AddTabButtonPart));
addTabButtonStyle.Add(IconButton.MarginProperty, TabControlResourceKey.AddTabButtonMarginHorizontal);
topStyle.Add(addTabButtonStyle);
topStyle.Add(itemPresenterPanelStyle);
commonStyle.Add(topStyle);
}
{
// 右
var rightStyle = new Style(selector => selector.Nesting().Class(BaseTabStrip.RightPC));
var containerStyle = new Style(selector => selector.Nesting().Template().Name(CardTabStripContainerPart));
containerStyle.Add(StackPanel.OrientationProperty, Orientation.Vertical);
rightStyle.Add(containerStyle);
var itemPresenterPanelStyle = new Style(selector => selector.Nesting().Template().Name(ItemsPresenterPart).Child().OfType<StackPanel>());
itemPresenterPanelStyle.Add(StackPanel.OrientationProperty, Orientation.Vertical);
itemPresenterPanelStyle.Add(StackPanel.SpacingProperty, TabControlResourceKey.CardGutter);
rightStyle.Add(itemPresenterPanelStyle);
var addTabButtonStyle = new Style(selector => selector.Nesting().Template().Name(AddTabButtonPart));
addTabButtonStyle.Add(IconButton.MarginProperty, TabControlResourceKey.AddTabButtonMarginVertical);
rightStyle.Add(addTabButtonStyle);
commonStyle.Add(rightStyle);
}
{
// 下
var bottomStyle = new Style(selector => selector.Nesting().Class(BaseTabStrip.BottomPC));
var containerStyle = new Style(selector => selector.Nesting().Template().Name(CardTabStripContainerPart));
containerStyle.Add(StackPanel.OrientationProperty, Orientation.Horizontal);
bottomStyle.Add(containerStyle);
var itemPresenterPanelStyle = new Style(selector => selector.Nesting().Template().Name(ItemsPresenterPart).Child().OfType<StackPanel>());
itemPresenterPanelStyle.Add(StackPanel.OrientationProperty, Orientation.Horizontal);
itemPresenterPanelStyle.Add(StackPanel.SpacingProperty, TabControlResourceKey.CardGutter);
bottomStyle.Add(itemPresenterPanelStyle);
var addTabButtonStyle = new Style(selector => selector.Nesting().Template().Name(AddTabButtonPart));
addTabButtonStyle.Add(IconButton.MarginProperty, TabControlResourceKey.AddTabButtonMarginHorizontal);
bottomStyle.Add(addTabButtonStyle);
commonStyle.Add(bottomStyle);
}
{
// 左
var leftStyle = new Style(selector => selector.Nesting().Class(BaseTabStrip.LeftPC));
var containerStyle = new Style(selector => selector.Nesting().Template().Name(CardTabStripContainerPart));
containerStyle.Add(StackPanel.OrientationProperty, Orientation.Vertical);
leftStyle.Add(containerStyle);
var itemPresenterPanelStyle = new Style(selector => selector.Nesting().Template().Name(ItemsPresenterPart).Child().OfType<StackPanel>());
itemPresenterPanelStyle.Add(StackPanel.OrientationProperty, Orientation.Vertical);
itemPresenterPanelStyle.Add(StackPanel.SpacingProperty, TabControlResourceKey.CardGutter);
leftStyle.Add(itemPresenterPanelStyle);
var addTabButtonStyle = new Style(selector => selector.Nesting().Template().Name(AddTabButtonPart));
addTabButtonStyle.Add(IconButton.MarginProperty, TabControlResourceKey.AddTabButtonMarginVertical);
leftStyle.Add(addTabButtonStyle);
commonStyle.Add(leftStyle);
}
Add(commonStyle);
}
}

View File

@ -1,5 +1,6 @@
using AtomUI.Theme.Styling;
using AtomUI.Theme.Utils;
using AtomUI.Utils;
using Avalonia;
using Avalonia.Animation;
using Avalonia.Animation.Easings;
@ -12,6 +13,22 @@ namespace AtomUI.Controls;
public class TabStrip : BaseTabStrip
{
#region
internal static readonly DirectProperty<TabStrip, double> SelectedIndicatorThicknessProperty =
AvaloniaProperty.RegisterDirect<TabStrip, double>(nameof(SelectedIndicatorThickness),
o => o.SelectedIndicatorThickness,
(o, v) => o.SelectedIndicatorThickness = v);
private double _selectedIndicatorThickness;
internal double SelectedIndicatorThickness
{
get => _selectedIndicatorThickness;
set => SetAndRaise(SelectedIndicatorThicknessProperty, ref _selectedIndicatorThickness, value);
}
#endregion
private Border? _selectedIndicator;
private ItemsPresenter? _itemsPresenter;
@ -48,15 +65,23 @@ public class TabStrip : BaseTabStrip
var selectedBounds = tabStripItem.Bounds;
var builder = new TransformOperations.Builder(1);
var offset = _itemsPresenter?.Bounds.Position ?? default;
if (TabStripPlacement == Dock.Top) {
_selectedIndicator.Width = tabStripItem.DesiredSize.Width;
_selectedIndicator.SetValue(Border.WidthProperty, tabStripItem.DesiredSize.Width);
_selectedIndicator.SetValue(Border.HeightProperty, _selectedIndicatorThickness);
builder.AppendTranslate(offset.X + selectedBounds.Left, 0);
} else if (TabStripPlacement == Dock.Right) {
_selectedIndicator.Height = tabStripItem.DesiredSize.Height;
_selectedIndicator.SetValue(Border.HeightProperty, tabStripItem.DesiredSize.Height);
_selectedIndicator.SetValue(Border.WidthProperty, _selectedIndicatorThickness);
builder.AppendTranslate(0, offset.Y + selectedBounds.Y);
} else if (TabStripPlacement == Dock.Bottom) {
_selectedIndicator.Width = tabStripItem.DesiredSize.Width;
_selectedIndicator.SetValue(Border.WidthProperty, tabStripItem.DesiredSize.Width);
_selectedIndicator.SetValue(Border.HeightProperty, _selectedIndicatorThickness);
builder.AppendTranslate(offset.X + selectedBounds.Left, 0);
} else {
_selectedIndicator.Height = tabStripItem.DesiredSize.Height;
_selectedIndicator.SetValue(Border.HeightProperty, tabStripItem.DesiredSize.Height);
_selectedIndicator.SetValue(Border.WidthProperty, _selectedIndicatorThickness);
builder.AppendTranslate(0, offset.Y + selectedBounds.Y);
}
_selectedIndicator.RenderTransform = builder.Build();
}
@ -80,10 +105,20 @@ public class TabStrip : BaseTabStrip
return tabStripItem;
}
protected override void PrepareContainerForItemOverride(Control container, object? item, int index)
{
base.PrepareContainerForItemOverride(container, item, index);
if (container is TabStripItem tabStripItem) {
tabStripItem.Shape = TabSharp.Line;
}
}
protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
{
base.OnApplyTemplate(e);
_selectedIndicator = e.NameScope.Find<Border>(TabStripTheme.SelectedItemIndicatorPart);
_itemsPresenter = e.NameScope.Find<ItemsPresenter>(TabStripTheme.ItemsPresenterPart);
TokenResourceBinder.CreateGlobalResourceBinding(this, SelectedIndicatorThicknessProperty, GlobalResourceKey.LineWidthBold);
}
}

View File

@ -8,6 +8,8 @@ using Avalonia;
using Avalonia.Animation;
using Avalonia.Controls;
using Avalonia.Controls.Primitives;
using Avalonia.Interactivity;
using Avalonia.Layout;
using Avalonia.LogicalTree;
using Avalonia.Rendering;
@ -25,7 +27,7 @@ public class TabStripItem : AvaloniaTabStripItem, IControlCustomStyle, ICustomHi
{
#region
public static readonly StyledProperty<SizeType> SizeTypeProperty =
TabStrip.SizeTypeProperty.AddOwner<TabStrip>();
BaseTabStrip.SizeTypeProperty.AddOwner<TabStripItem>();
public static readonly StyledProperty<PathIcon?> IconProperty =
AvaloniaProperty.Register<TabStripItem, PathIcon?>(nameof(Icon));
@ -69,6 +71,7 @@ public class TabStripItem : AvaloniaTabStripItem, IControlCustomStyle, ICustomHi
get => _tabStripPlacement;
internal set => SetAndRaise(TabStripPlacementProperty, ref _tabStripPlacement, value);
}
#endregion
#region
@ -84,7 +87,8 @@ public class TabStripItem : AvaloniaTabStripItem, IControlCustomStyle, ICustomHi
private StackPanel? _contentLayout;
private IControlCustomStyle _customStyle;
private IconButton? _closeButton;
public TabStripItem()
{
_customStyle = this;
@ -94,7 +98,7 @@ public class TabStripItem : AvaloniaTabStripItem, IControlCustomStyle, ICustomHi
{
if (Icon is not null) {
UIStructureUtils.SetTemplateParent(Icon, this);
Icon.Name = TabStripItemTheme.ItemIconPart;
Icon.Name = BaseTabStripItemTheme.ItemIconPart;
if (Icon.ThemeType != IconThemeType.TwoTone) {
TokenResourceBinder.CreateTokenBinding(Icon, PathIcon.NormalFilledBrushProperty, TabControlResourceKey.ItemColor);
TokenResourceBinder.CreateTokenBinding(Icon, PathIcon.ActiveFilledBrushProperty, TabControlResourceKey.ItemHoverColor);
@ -107,6 +111,27 @@ public class TabStripItem : AvaloniaTabStripItem, IControlCustomStyle, ICustomHi
}
}
private void SetupCloseIcon()
{
if (CloseIcon is null) {
CloseIcon = new PathIcon
{
Kind = "CloseOutlined"
};
TokenResourceBinder.CreateGlobalResourceBinding(CloseIcon, PathIcon.WidthProperty, GlobalResourceKey.IconSizeSM);
TokenResourceBinder.CreateGlobalResourceBinding(CloseIcon, PathIcon.HeightProperty, GlobalResourceKey.IconSizeSM);
}
CloseIcon.SetValue(PathIcon.VerticalAlignmentProperty, VerticalAlignment.Center);
UIStructureUtils.SetTemplateParent(CloseIcon, this);
if (CloseIcon.ThemeType != IconThemeType.TwoTone) {
TokenResourceBinder.CreateTokenBinding(CloseIcon, PathIcon.NormalFilledBrushProperty, GlobalResourceKey.ColorIcon);
TokenResourceBinder.CreateTokenBinding(CloseIcon, PathIcon.ActiveFilledBrushProperty, GlobalResourceKey.ColorIconHover);
TokenResourceBinder.CreateTokenBinding(CloseIcon, PathIcon.DisabledFilledBrushProperty, GlobalResourceKey.ColorTextDisabled);
}
}
protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
{
base.OnApplyTemplate(e);
@ -118,15 +143,41 @@ public class TabStripItem : AvaloniaTabStripItem, IControlCustomStyle, ICustomHi
void IControlCustomStyle.HandleTemplateApplied(INameScope scope)
{
_contentLayout = scope.Find<StackPanel>(BaseTabStripItemTheme.ContentLayoutPart);
_closeButton = scope.Find<IconButton>(BaseTabStripItemTheme.ItemCloseButtonPart);
SetupItemIcon();
SetupCloseIcon();
if (Transitions is null) {
var transitions = new Transitions();
transitions.Add(AnimationUtils.CreateTransition<SolidColorBrushTransition>(ForegroundProperty));
Transitions = transitions;
}
if (_closeButton is not null) {
_closeButton.Click += HandleCloseRequest;
}
}
#endregion
private void HandleCloseRequest(object? sender, RoutedEventArgs args)
{
if (Parent is BaseTabStrip tabStrip) {
if (tabStrip.SelectedItem is TabStripItem selectedItem) {
if (selectedItem == this) {
var selectedIndex = tabStrip.SelectedIndex;
object? newSelectedItem = null;
if (selectedIndex != 0) {
newSelectedItem = tabStrip.Items[--selectedIndex];
}
tabStrip.Items.Remove(this);
tabStrip.SelectedItem = newSelectedItem;
} else {
tabStrip.Items.Remove(this);
}
}
}
}
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
{
base.OnPropertyChanged(change);
@ -136,15 +187,30 @@ public class TabStripItem : AvaloniaTabStripItem, IControlCustomStyle, ICustomHi
if (oldIcon != null) {
UIStructureUtils.SetTemplateParent(oldIcon, null);
}
SetupItemIcon();
}
}
if (change.Property == CloseIconProperty) {
var oldIcon = change.GetOldValue<PathIcon?>();
if (oldIcon != null) {
UIStructureUtils.SetTemplateParent(oldIcon, null);
}
SetupCloseIcon();
}
if (change.Property == ShapeProperty) {
HandleShapeChanged();
}
}
protected override void OnAttachedToLogicalTree(LogicalTreeAttachmentEventArgs e)
{
base.OnAttachedToLogicalTree(e);
HandleShapeChanged();
}
private void HandleShapeChanged()
{
if (Shape == TabSharp.Line) {
TokenResourceBinder.CreateTokenBinding(this, ThemeProperty, TabStripItemTheme.ID);
} else {

View File

@ -0,0 +1,97 @@
using AtomUI.Theme.Styling;
using Avalonia.Controls;
using Avalonia.Styling;
namespace AtomUI.Controls;
[ControlThemeProvider]
internal class TabStripItemTheme : BaseTabStripItemTheme
{
public const string ID = "TabStripItem";
public TabStripItemTheme() : base(typeof(TabStripItem)) { }
public override string ThemeResourceKey()
{
return ID;
}
protected override void BuildStyles()
{
base.BuildStyles();
BuildSizeTypeStyle();
}
protected void BuildSizeTypeStyle()
{
var topOrBottomStyle = new Style(selector => Selectors.Or(selector.Nesting().PropertyEquals(TabStripItem.TabStripPlacementProperty, Dock.Top),
selector.Nesting().PropertyEquals(TabStripItem.TabStripPlacementProperty, Dock.Bottom)));
{
topOrBottomStyle.Add(Border.MarginProperty, TabControlResourceKey.HorizontalItemMargin);
var largeSizeStyle = new Style(selector => selector.Nesting().PropertyEquals(TabStripItem.SizeTypeProperty, SizeType.Large));
{
var decoratorStyle = new Style(selector => selector.Nesting().Template().Name(DecoratorPart));
decoratorStyle.Add(Border.PaddingProperty, TabControlResourceKey.HorizontalItemPaddingLG);
largeSizeStyle.Add(decoratorStyle);
}
topOrBottomStyle.Add(largeSizeStyle);
var middleSizeStyle = new Style(selector => selector.Nesting().PropertyEquals(TabStripItem.SizeTypeProperty, SizeType.Middle));
{
var decoratorStyle = new Style(selector => selector.Nesting().Template().Name(DecoratorPart));
decoratorStyle.Add(Border.PaddingProperty, TabControlResourceKey.HorizontalItemPadding);
middleSizeStyle.Add(decoratorStyle);
}
topOrBottomStyle.Add(middleSizeStyle);
var smallSizeType = new Style(selector => selector.Nesting().PropertyEquals(TabStripItem.SizeTypeProperty, SizeType.Small));
{
var decoratorStyle = new Style(selector => selector.Nesting().Template().Name(DecoratorPart));
decoratorStyle.Add(Border.PaddingProperty, TabControlResourceKey.HorizontalItemPaddingSM);
smallSizeType.Add(decoratorStyle);
}
topOrBottomStyle.Add(smallSizeType);
Add(topOrBottomStyle);
}
var leftOrRightStyle = new Style(selector => Selectors.Or(selector.Nesting().PropertyEquals(TabStripItem.TabStripPlacementProperty, Dock.Left),
selector.Nesting().PropertyEquals(TabStripItem.TabStripPlacementProperty, Dock.Right)));
{
// 貌似没必要分大小,但是先放着吧,万一需要难得再加
var largeSizeStyle = new Style(selector => selector.Nesting().PropertyEquals(TabStripItem.SizeTypeProperty, SizeType.Large));
{
var decoratorStyle = new Style(selector => selector.Nesting().Template().Name(DecoratorPart));
decoratorStyle.Add(Border.PaddingProperty, TabControlResourceKey.VerticalItemPadding);
largeSizeStyle.Add(decoratorStyle);
}
leftOrRightStyle.Add(largeSizeStyle);
var middleSizeStyle = new Style(selector => selector.Nesting().PropertyEquals(TabStripItem.SizeTypeProperty, SizeType.Middle));
{
var decoratorStyle = new Style(selector => selector.Nesting().Template().Name(DecoratorPart));
decoratorStyle.Add(Border.PaddingProperty, TabControlResourceKey.VerticalItemPadding);
middleSizeStyle.Add(decoratorStyle);
}
leftOrRightStyle.Add(middleSizeStyle);
var smallSizeType = new Style(selector => selector.Nesting().PropertyEquals(TabStripItem.SizeTypeProperty, SizeType.Small));
{
var decoratorStyle = new Style(selector => selector.Nesting().Template().Name(DecoratorPart));
decoratorStyle.Add(Border.PaddingProperty, TabControlResourceKey.VerticalItemPadding);
smallSizeType.Add(decoratorStyle);
}
leftOrRightStyle.Add(smallSizeType);
Add(leftOrRightStyle);
}
}
}

View File

@ -0,0 +1,22 @@
using Avalonia.Controls;
using Avalonia.Threading;
namespace AtomUI.Controls;
internal class TabStripOverflowMenuItem : BaseOverflowMenuItem
{
protected override Type StyleKeyOverride => typeof(BaseOverflowMenuItem);
public TabStripItem? TabStripItem { get; set; }
protected override void NotifyCloseRequest()
{
if (Parent is MenuBase menu) {
var eventArgs = new CloseTabRequestEventArgs(CloseTabEvent, TabStripItem!);
RaiseEvent(eventArgs);
Dispatcher.UIThread.Post(() =>
{
menu.Close();
});
}
}
}

View File

@ -0,0 +1,106 @@
using Avalonia.Controls;
using Avalonia.Controls.Primitives;
using Avalonia.Interactivity;
using Avalonia.Threading;
namespace AtomUI.Controls;
internal class TabStripScrollViewer : BaseTabScrollViewer
{
#region
internal BaseTabStrip? TabStrip { get; set; }
#endregion
protected override Type StyleKeyOverride => typeof(BaseTabScrollViewer);
protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
{
base.OnApplyTemplate(e);
if (_menuIndicator is not null) {
_menuIndicator.Click += HandleMenuIndicator;
}
}
private void HandleMenuIndicator(object? sender, RoutedEventArgs args)
{
if (_menuFlyout is null) {
_menuFlyout = new MenuFlyout();
}
if (TabStripPlacement == Dock.Top || TabStripPlacement == Dock.Bottom) {
_menuFlyout.Placement = PlacementMode.Bottom;
} else {
_menuFlyout.Placement = PlacementMode.Right;
}
// 收集没有完全显示的 Tab 列表
_menuFlyout.Items.Clear();
if (TabStrip is not null) {
for (int i = 0; i < TabStrip.ItemCount; i++) {
var itemContainer = TabStrip.ContainerFromIndex(i)!;
if (itemContainer is TabStripItem tabStripItem) {
var itemBounds = itemContainer.Bounds;
var left = Math.Floor(itemBounds.Left - Offset.X);
var right = Math.Floor(itemBounds.Right - Offset.X);
if (TabStripPlacement == Dock.Top || TabStripPlacement == Dock.Bottom) {
if (left < 0 || right > Viewport.Width) {
var menuItem = new TabStripOverflowMenuItem()
{
Header = tabStripItem.Content,
TabStripItem = tabStripItem,
IsClosable = tabStripItem.IsClosable
};
menuItem.Click += HandleMenuItemClicked;
menuItem.CloseTab += HandleCloseTabRequest;
_menuFlyout.Items.Add(menuItem);
}
}
}
}
if (_menuFlyout.Items.Count > 0) {
_menuFlyout.ShowAt(_menuIndicator!);
}
}
}
private void HandleMenuItemClicked(object? sender, RoutedEventArgs args)
{
if (TabStrip is not null) {
Dispatcher.UIThread.Post(sender =>
{
if (sender is TabStripOverflowMenuItem tabStripMenuItem) {
var tabStripItem = tabStripMenuItem.TabStripItem;
if (tabStripItem is not null) {
tabStripItem.BringIntoView();
TabStrip.SelectedItem = tabStripItem;
}
}
}, sender);
}
}
private void HandleCloseTabRequest(object? sender, RoutedEventArgs args)
{
if (sender is TabStripOverflowMenuItem tabStripMenuItem) {
if (TabStrip is not null) {
if (TabStrip.SelectedItem is TabStripItem selectedItem) {
if (selectedItem == tabStripMenuItem.TabStripItem) {
var selectedIndex = TabStrip.SelectedIndex;
object? newSelectedItem = null;
if (selectedIndex != 0) {
newSelectedItem = TabStrip.Items[--selectedIndex];
}
TabStrip.Items.Remove(tabStripMenuItem.TabStripItem);
TabStrip.SelectedItem = newSelectedItem;
} else {
TabStrip.Items.Remove(tabStripMenuItem.TabStripItem);
}
}
}
}
}
}

View File

@ -17,12 +17,23 @@ internal class TabStripTheme : BaseTabStripTheme
protected override void NotifyBuildControlTemplate(BaseTabStrip baseTabStrip, INameScope scope, Border container)
{
var tabScrollViewer = new TabScrollViewer();
CreateTemplateParentBinding(tabScrollViewer, TabScrollViewer.TabStripPlacementProperty, TabStrip.TabStripPlacementProperty);
var alignWrapper = new Panel()
{
Name = AlignWrapperPart
};
alignWrapper.RegisterInNameScope(scope);
var tabScrollViewer = new TabStripScrollViewer()
{
Name = TabsContainerPart
};
CreateTemplateParentBinding(tabScrollViewer, BaseTabScrollViewer.TabStripPlacementProperty, TabStrip.TabStripPlacementProperty);
var contentPanel = CreateTabStripContentPanel(scope);
tabScrollViewer.Content = contentPanel;
tabScrollViewer.TabStrip = baseTabStrip;
container.Child = tabScrollViewer;
alignWrapper.Children.Add(tabScrollViewer);
container.Child = alignWrapper;
}
private Panel CreateTabStripContentPanel(INameScope scope)
@ -56,22 +67,18 @@ internal class TabStripTheme : BaseTabStripTheme
{
// 上
var topStyle = new Style(selector => selector.Nesting().Class(BaseTabStrip.TopPC));
var itemPresenterPanelStyle = new Style(selector => selector.Nesting().Template().OfType<ItemsPresenter>().Name(ItemsPresenterPart).Child().OfType<StackPanel>());
var itemPresenterPanelStyle = new Style(selector => selector.Nesting().Template().Name(ItemsPresenterPart).Child().OfType<StackPanel>());
itemPresenterPanelStyle.Add(StackPanel.SpacingProperty, TabControlResourceKey.HorizontalItemGutter);
itemPresenterPanelStyle.Add(StackPanel.OrientationProperty, Orientation.Horizontal);
topStyle.Add(itemPresenterPanelStyle);
var indicatorStyle = new Style(selector => selector.Nesting().Template().Name(SelectedItemIndicatorPart));
indicatorStyle.Add(Border.HeightProperty, GlobalResourceKey.LineWidthBold);
indicatorStyle.Add(Border.HorizontalAlignmentProperty, HorizontalAlignment.Left);
indicatorStyle.Add(Border.VerticalAlignmentProperty, VerticalAlignment.Bottom);
topStyle.Add(indicatorStyle);
// tabs 是否居中
var tabAlignCenterStyle = new Style(selector => selector.Nesting().PropertyEquals(TabStrip.TabAlignmentCenterProperty, true));
var itemsPresenterStyle = new Style(selector => selector.Nesting().Template().Name(ItemsPresenterPart));
itemsPresenterStyle.Add(ItemsPresenter.HorizontalAlignmentProperty, HorizontalAlignment.Center);
tabAlignCenterStyle.Add(itemsPresenterStyle);
topStyle.Add(tabAlignCenterStyle);
topStyle.Add(itemPresenterPanelStyle);
commonStyle.Add(topStyle);
}
@ -79,67 +86,49 @@ internal class TabStripTheme : BaseTabStripTheme
{
// 右
var rightStyle = new Style(selector => selector.Nesting().Class(BaseTabStrip.RightPC));
var itemPresenterPanelStyle = new Style(selector => selector.Nesting().Template().OfType<ItemsPresenter>().Name(ItemsPresenterPart).Child().OfType<StackPanel>());
itemPresenterPanelStyle.Add(StackPanel.SpacingProperty, TabControlResourceKey.HorizontalItemGutter);
var itemPresenterPanelStyle = new Style(selector => selector.Nesting().Template().Name(ItemsPresenterPart).Child().OfType<StackPanel>());
itemPresenterPanelStyle.Add(StackPanel.OrientationProperty, Orientation.Vertical);
itemPresenterPanelStyle.Add(StackPanel.SpacingProperty, TabControlResourceKey.VerticalItemGutter);
rightStyle.Add(itemPresenterPanelStyle);
var indicatorStyle = new Style(selector => selector.Nesting().Template().Name(SelectedItemIndicatorPart));
indicatorStyle.Add(Border.WidthProperty, GlobalResourceKey.LineWidthBold);
indicatorStyle.Add(Border.HorizontalAlignmentProperty, HorizontalAlignment.Left);
indicatorStyle.Add(Border.VerticalAlignmentProperty, VerticalAlignment.Top);
rightStyle.Add(indicatorStyle);
// tabs 是否居中
var tabAlignCenterStyle = new Style(selector => selector.Nesting().PropertyEquals(TabStrip.TabAlignmentCenterProperty, true));
var itemsPresenterStyle = new Style(selector => selector.Nesting().Template().Name(ItemsPresenterPart));
itemsPresenterStyle.Add(ItemsPresenter.VerticalAlignmentProperty, VerticalAlignment.Center);
tabAlignCenterStyle.Add(itemsPresenterStyle);
rightStyle.Add(tabAlignCenterStyle);
commonStyle.Add(rightStyle);
}
{
// 下
var bottomStyle = new Style(selector => selector.Nesting().Class(BaseTabStrip.BottomPC));
var itemPresenterPanelStyle = new Style(selector => selector.Nesting().Template().OfType<ItemsPresenter>().Name(ItemsPresenterPart).Child().OfType<StackPanel>());
var itemPresenterPanelStyle = new Style(selector => selector.Nesting().Template().Name(ItemsPresenterPart).Child().OfType<StackPanel>());
itemPresenterPanelStyle.Add(StackPanel.SpacingProperty, TabControlResourceKey.HorizontalItemGutter);
itemPresenterPanelStyle.Add(StackPanel.OrientationProperty, Orientation.Horizontal);
bottomStyle.Add(itemPresenterPanelStyle);
var indicatorStyle = new Style(selector => selector.Nesting().Template().Name(SelectedItemIndicatorPart));
indicatorStyle.Add(Border.HeightProperty, GlobalResourceKey.LineWidthBold);
indicatorStyle.Add(Border.HorizontalAlignmentProperty, HorizontalAlignment.Left);
indicatorStyle.Add(Border.VerticalAlignmentProperty, VerticalAlignment.Top);
bottomStyle.Add(indicatorStyle);
// tabs 是否居中
var tabAlignCenterStyle = new Style(selector => selector.Nesting().PropertyEquals(TabStrip.TabAlignmentCenterProperty, true));
var itemsPresenterStyle = new Style(selector => selector.Nesting().Template().Name(ItemsPresenterPart));
itemsPresenterStyle.Add(ItemsPresenter.HorizontalAlignmentProperty, HorizontalAlignment.Center);
tabAlignCenterStyle.Add(itemsPresenterStyle);
bottomStyle.Add(tabAlignCenterStyle);
commonStyle.Add(bottomStyle);
}
{
// 左
var leftStyle = new Style(selector => selector.Nesting().Class(BaseTabStrip.LeftPC));
var itemPresenterPanelStyle = new Style(selector => selector.Nesting().Template().OfType<ItemsPresenter>().Name(ItemsPresenterPart).Child().OfType<StackPanel>());
itemPresenterPanelStyle.Add(StackPanel.SpacingProperty, TabControlResourceKey.HorizontalItemGutter);
var itemPresenterPanelStyle = new Style(selector => selector.Nesting().Template().Name(ItemsPresenterPart).Child().OfType<StackPanel>());
itemPresenterPanelStyle.Add(StackPanel.OrientationProperty, Orientation.Vertical);
itemPresenterPanelStyle.Add(StackPanel.SpacingProperty, TabControlResourceKey.VerticalItemGutter);
leftStyle.Add(itemPresenterPanelStyle);
var indicatorStyle = new Style(selector => selector.Nesting().Template().Name(SelectedItemIndicatorPart));
indicatorStyle.Add(Border.WidthProperty, GlobalResourceKey.LineWidthBold);
indicatorStyle.Add(Border.HorizontalAlignmentProperty, HorizontalAlignment.Right);
indicatorStyle.Add(Border.VerticalAlignmentProperty, VerticalAlignment.Top);
leftStyle.Add(indicatorStyle);
// tabs 是否居中
var tabAlignCenterStyle = new Style(selector => selector.Nesting().PropertyEquals(TabStrip.TabAlignmentCenterProperty, true));
var itemsPresenterStyle = new Style(selector => selector.Nesting().Template().Name(ItemsPresenterPart));
itemsPresenterStyle.Add(ItemsPresenter.VerticalAlignmentProperty, VerticalAlignment.Center);
tabAlignCenterStyle.Add(itemsPresenterStyle);
leftStyle.Add(tabAlignCenterStyle);
commonStyle.Add(leftStyle);
}

View File

@ -1,125 +0,0 @@
using AtomUI.Theme.Styling;
using Avalonia.Controls;
using Avalonia.Layout;
using Avalonia.Styling;
namespace AtomUI.Controls;
[ControlThemeProvider]
internal class TabStripItemTheme : BaseTabStripItemTheme
{
public const string ID = "TabStripItem";
public TabStripItemTheme() : base(typeof(TabStripItem)) { }
public override string ThemeResourceKey()
{
return ID;
}
protected override void BuildStyles()
{
base.BuildStyles();
// Icon 一些通用属性
{
var iconStyle = new Style(selector => selector.Nesting().Template().OfType<PathIcon>());
iconStyle.Add(PathIcon.MarginProperty, TabControlResourceKey.ItemIconMargin);
Add(iconStyle);
}
var decoratorStyle = new Style(selector => selector.Nesting().Template().OfType<Border>().Name(DecoratorPart));
decoratorStyle.Add(Border.MarginProperty, TabControlResourceKey.HorizontalItemMargin);
Add(decoratorStyle);
BuildSizeTypeStyle();
BuildPlacementStyle();
}
protected void BuildSizeTypeStyle()
{
var largeSizeStyle = new Style(selector => selector.Nesting().PropertyEquals(TabStripItem.SizeTypeProperty, SizeType.Large));
{
var decoratorStyle = new Style(selector => selector.Nesting().Template().OfType<Border>().Name(DecoratorPart));
decoratorStyle.Add(Border.PaddingProperty, TabControlResourceKey.HorizontalItemPaddingLG);
largeSizeStyle.Add(decoratorStyle);
}
{
// Icon
var iconStyle = new Style(selector => selector.Nesting().Template().Name(ItemIconPart));
iconStyle.Add(PathIcon.WidthProperty, GlobalResourceKey.IconSize);
iconStyle.Add(PathIcon.HeightProperty, GlobalResourceKey.IconSize);
largeSizeStyle.Add(iconStyle);
}
Add(largeSizeStyle);
var middleSizeStyle = new Style(selector => selector.Nesting().PropertyEquals(TabStripItem.SizeTypeProperty, SizeType.Middle));
{
var decoratorStyle = new Style(selector => selector.Nesting().Template().OfType<Border>().Name(DecoratorPart));
decoratorStyle.Add(Border.PaddingProperty, TabControlResourceKey.HorizontalItemPadding);
middleSizeStyle.Add(decoratorStyle);
}
{
// Icon
var iconStyle = new Style(selector => selector.Nesting().Template().Name(ItemIconPart));
iconStyle.Add(PathIcon.WidthProperty, GlobalResourceKey.IconSize);
iconStyle.Add(PathIcon.HeightProperty, GlobalResourceKey.IconSize);
middleSizeStyle.Add(iconStyle);
}
Add(middleSizeStyle);
var smallSizeType = new Style(selector => selector.Nesting().PropertyEquals(TabStripItem.SizeTypeProperty, SizeType.Small));
{
var decoratorStyle = new Style(selector => selector.Nesting().Template().OfType<Border>().Name(DecoratorPart));
decoratorStyle.Add(Border.PaddingProperty, TabControlResourceKey.HorizontalItemPaddingSM);
smallSizeType.Add(decoratorStyle);
}
{
// Icon
var iconStyle = new Style(selector => selector.Nesting().Template().Name(ItemIconPart));
iconStyle.Add(PathIcon.WidthProperty, GlobalResourceKey.IconSizeSM);
iconStyle.Add(PathIcon.HeightProperty, GlobalResourceKey.IconSizeSM);
smallSizeType.Add(iconStyle);
}
Add(smallSizeType);
}
private void BuildPlacementStyle()
{
// 设置 items presenter 面板样式
// 分为上、右、下、左
{
// 上
var topStyle = new Style(selector => selector.Nesting().PropertyEquals(TabStripItem.TabStripPlacementProperty, Dock.Top));
var iconStyle = new Style(selector => selector.Nesting().Template().OfType<PathIcon>());
iconStyle.Add(PathIcon.VerticalAlignmentProperty, VerticalAlignment.Center);
topStyle.Add(iconStyle);
Add(topStyle);
}
{
// 右
var rightStyle = new Style(selector => selector.Nesting().PropertyEquals(TabStripItem.TabStripPlacementProperty, Dock.Right));
var iconStyle = new Style(selector => selector.Nesting().Template().OfType<PathIcon>());
iconStyle.Add(PathIcon.HorizontalAlignmentProperty, HorizontalAlignment.Center);
rightStyle.Add(iconStyle);
Add(rightStyle);
}
{
// 下
var bottomStyle = new Style(selector => selector.Nesting().PropertyEquals(TabStripItem.TabStripPlacementProperty, Dock.Bottom));
var iconStyle = new Style(selector => selector.Nesting().Template().OfType<PathIcon>());
iconStyle.Add(PathIcon.VerticalAlignmentProperty, VerticalAlignment.Center);
bottomStyle.Add(iconStyle);
Add(bottomStyle);
}
{
// 左
var leftStyle = new Style(selector => selector.Nesting().PropertyEquals(TabStripItem.TabStripPlacementProperty, Dock.Left));
var iconStyle = new Style(selector => selector.Nesting().Template().OfType<PathIcon>());
iconStyle.Add(PathIcon.HorizontalAlignmentProperty, HorizontalAlignment.Center);
leftStyle.Add(iconStyle);
Add(leftStyle);
}
}
}

View File

@ -1,56 +0,0 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.Primitives;
using Avalonia.Interactivity;
using Avalonia.LogicalTree;
namespace AtomUI.Controls;
internal class TabStripMenuItem : MenuItem
{
#region
public static readonly DirectProperty<TabStripMenuItem, bool> IsClosableProperty =
AvaloniaProperty.RegisterDirect<TabStripMenuItem, bool>(nameof(IsClosable),
o => o.IsClosable,
(o, v) => o.IsClosable = v);
public static readonly RoutedEvent<RoutedEventArgs> CloseTabEvent =
RoutedEvent.Register<Button, RoutedEventArgs>(nameof(CloseTab), RoutingStrategies.Bubble);
private bool _isClosable = false;
public bool IsClosable
{
get => _isClosable;
set => SetAndRaise(IsClosableProperty, ref _isClosable, value);
}
public TabStripItem? TabStripItem { get; set; }
public event EventHandler<RoutedEventArgs>? CloseTab
{
add => AddHandler(CloseTabEvent, value);
remove => RemoveHandler(CloseTabEvent, value);
}
#endregion
private IconButton? _iconButton;
protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
{
base.OnApplyTemplate(e);
_iconButton = e.NameScope.Find<IconButton>(TabStripMenuItemTheme.ItemCloseButtonPart);
if (_iconButton is not null) {
_iconButton.Click += (sender, args) =>
{
var menu = this.FindLogicalAncestorOfType<MenuBase>();
if (menu is not null) {
menu.Close();
var eventArgs = new RoutedEventArgs(CloseTabEvent);
RaiseEvent(eventArgs);
}
};
}
}
}

View File

@ -0,0 +1,125 @@
using Avalonia;
using Avalonia.Controls;
namespace AtomUI.Controls;
internal class TabsContainerPanel : Panel
{
#region
public static readonly DirectProperty<TabsContainerPanel, BaseTabScrollViewer?> TabScrollViewerProperty =
AvaloniaProperty.RegisterDirect<TabsContainerPanel, BaseTabScrollViewer?>(nameof(TabScrollViewer),
o => o.TabScrollViewer,
(o, v) => o.TabScrollViewer = v);
private BaseTabScrollViewer? _tabScrollViewer;
public BaseTabScrollViewer? TabScrollViewer
{
get => _tabScrollViewer;
set => SetAndRaise(TabScrollViewerProperty, ref _tabScrollViewer, value);
}
public static readonly DirectProperty<TabsContainerPanel, IconButton?> AddTabButtonProperty =
AvaloniaProperty.RegisterDirect<TabsContainerPanel, IconButton?>(nameof(AddTabButton),
o => o.AddTabButton,
(o, v) => o.AddTabButton = v);
private IconButton? _addTabButton;
public IconButton? AddTabButton
{
get => _addTabButton;
set => SetAndRaise(AddTabButtonProperty, ref _addTabButton, value);
}
#endregion
#region
internal static readonly DirectProperty<TabsContainerPanel, Dock> TabStripPlacementProperty =
AvaloniaProperty.RegisterDirect<TabsContainerPanel, Dock>(nameof(TabStripPlacement),
o => o.TabStripPlacement,
(o, v) => o.TabStripPlacement = v);
private Dock _tabStripPlacement;
internal Dock TabStripPlacement
{
get => _tabStripPlacement;
set => SetAndRaise(TabStripPlacementProperty, ref _tabStripPlacement, value);
}
#endregion
static TabsContainerPanel()
{
AffectsMeasure<TabsContainerPanel>(TabScrollViewerProperty, AddTabButtonProperty);
AffectsArrange<TabsContainerPanel>(TabStripPlacementProperty);
}
protected override Size ArrangeOverride(Size arrangeSize)
{
// TODO 暂时不做验证,默认认为两个元素都存在
// 理论上这里要报错,但是我们是内部使用
if (_tabScrollViewer is not null && _addTabButton is not null) {
if (TabStripPlacement == Dock.Top || TabStripPlacement == Dock.Bottom) {
var scrollViewerDesiredWidth = _tabScrollViewer.DesiredSize.Width;
var addTabButtonDesiredWidth = _addTabButton.DesiredSize.Width;
var totalDesiredWidth = scrollViewerDesiredWidth + addTabButtonDesiredWidth;
var btnOffsetY = 0d;
if (TabStripPlacement == Dock.Top) {
btnOffsetY = arrangeSize.Height - _addTabButton.DesiredSize.Height;
}
if (totalDesiredWidth > arrangeSize.Width) {
_tabScrollViewer.Arrange(new Rect(new Point(0, 0), new Size(arrangeSize.Width - addTabButtonDesiredWidth, arrangeSize.Height)));
_addTabButton.Arrange(new Rect(new Point(arrangeSize.Width - addTabButtonDesiredWidth, btnOffsetY), _addTabButton.DesiredSize));
} else {
_tabScrollViewer.Arrange(new Rect(new Point(0, 0), _tabScrollViewer.DesiredSize));
_addTabButton.Arrange(new Rect(new Point(scrollViewerDesiredWidth, btnOffsetY), _addTabButton.DesiredSize));
}
} else {
var scrollViewerDesiredHeight = _tabScrollViewer.DesiredSize.Height;
var addTabButtonDesiredHeight = _addTabButton.DesiredSize.Height;
var totalDesiredHeight = scrollViewerDesiredHeight + addTabButtonDesiredHeight;
var btnOffsetX = 0d;
if (TabStripPlacement == Dock.Left) {
btnOffsetX = arrangeSize.Width - _addTabButton.DesiredSize.Width;
}
if (totalDesiredHeight > arrangeSize.Height) {
_tabScrollViewer.Arrange(new Rect(new Point(0, 0), new Size(arrangeSize.Width, arrangeSize.Height - addTabButtonDesiredHeight)));
_addTabButton.Arrange(new Rect(new Point(btnOffsetX, arrangeSize.Height - addTabButtonDesiredHeight), _addTabButton.DesiredSize));
} else {
_tabScrollViewer.Arrange(new Rect(new Point(0, 0), _tabScrollViewer.DesiredSize));
_addTabButton.Arrange(new Rect(new Point(btnOffsetX, scrollViewerDesiredHeight), _addTabButton.DesiredSize));
}
}
}
return arrangeSize;
}
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
{
base.OnPropertyChanged(change);
if (change.Property == TabScrollViewerProperty) {
var oldScrollViewer = change.GetOldValue<BaseTabScrollViewer?>();
if (oldScrollViewer is not null) {
Children.Remove(oldScrollViewer);
}
if (TabScrollViewer is not null) {
Children.Add(TabScrollViewer);
}
} else if (change.Property == AddTabButtonProperty) {
var oldAddTabButton = change.GetOldValue<IconButton?>();
if (oldAddTabButton is not null) {
Children.Remove(oldAddTabButton);
}
if (AddTabButton is not null) {
Children.Add(AddTabButton);
}
}
}
}