mirror of
https://gitee.com/chinware/atomui.git
synced 2024-11-29 18:38:16 +08:00
Merge branch 'feature/tabcontrol' into develop
This commit is contained in:
commit
4010ade7e9
@ -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>
|
@ -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
|
||||
}
|
@ -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)) {}
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
|
@ -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))
|
||||
|
@ -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))
|
||||
|
@ -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";
|
||||
|
||||
|
@ -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)) { }
|
||||
|
@ -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)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
63
src/AtomUI.Controls/TabControl/BaseOverflowMenuItem.cs
Normal file
63
src/AtomUI.Controls/TabControl/BaseOverflowMenuItem.cs
Normal 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; }
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
145
src/AtomUI.Controls/TabControl/BaseTabControlTheme.cs
Normal file
145
src/AtomUI.Controls/TabControl/BaseTabControlTheme.cs
Normal 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);
|
||||
}
|
||||
}
|
197
src/AtomUI.Controls/TabControl/BaseTabItemTheme.cs
Normal file
197
src/AtomUI.Controls/TabControl/BaseTabItemTheme.cs
Normal 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);
|
||||
}
|
||||
}
|
@ -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);
|
@ -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);
|
@ -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);
|
||||
}
|
||||
}
|
202
src/AtomUI.Controls/TabControl/CardTabControl.cs
Normal file
202
src/AtomUI.Controls/TabControl/CardTabControl.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
180
src/AtomUI.Controls/TabControl/CardTabControlTheme.cs
Normal file
180
src/AtomUI.Controls/TabControl/CardTabControlTheme.cs
Normal 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);
|
||||
}
|
||||
}
|
181
src/AtomUI.Controls/TabControl/CardTabItemTheme.cs
Normal file
181
src/AtomUI.Controls/TabControl/CardTabItemTheme.cs
Normal 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);
|
||||
}
|
||||
}
|
@ -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
|
||||
};
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
22
src/AtomUI.Controls/TabControl/TabControlOverflowMenuItem.cs
Normal file
22
src/AtomUI.Controls/TabControl/TabControlOverflowMenuItem.cs
Normal 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();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
122
src/AtomUI.Controls/TabControl/TabControlScrollViewer.cs
Normal file
122
src/AtomUI.Controls/TabControl/TabControlScrollViewer.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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()
|
||||
{
|
121
src/AtomUI.Controls/TabControl/TabStrip/BaseTabStripTheme.cs
Normal file
121
src/AtomUI.Controls/TabControl/TabStrip/BaseTabStripTheme.cs
Normal 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);
|
||||
}
|
||||
}
|
202
src/AtomUI.Controls/TabControl/TabStrip/CardTabStrip.cs
Normal file
202
src/AtomUI.Controls/TabControl/TabStrip/CardTabStrip.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
181
src/AtomUI.Controls/TabControl/TabStrip/CardTabStripItemTheme.cs
Normal file
181
src/AtomUI.Controls/TabControl/TabStrip/CardTabStripItemTheme.cs
Normal 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);
|
||||
}
|
||||
}
|
176
src/AtomUI.Controls/TabControl/TabStrip/CardTabStripTheme.cs
Normal file
176
src/AtomUI.Controls/TabControl/TabStrip/CardTabStripTheme.cs
Normal 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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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 {
|
97
src/AtomUI.Controls/TabControl/TabStrip/TabStripItemTheme.cs
Normal file
97
src/AtomUI.Controls/TabControl/TabStrip/TabStripItemTheme.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
106
src/AtomUI.Controls/TabControl/TabStrip/TabStripScrollViewer.cs
Normal file
106
src/AtomUI.Controls/TabControl/TabStrip/TabStripScrollViewer.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
125
src/AtomUI.Controls/TabControl/TabsContainerPanel.cs
Normal file
125
src/AtomUI.Controls/TabControl/TabsContainerPanel.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user