An interactive map view via Avalonia

385 Views Asked by At

I need to make an interactive map of the city. Some of the buildings are just the background. Some buildings are interactive, they trigger an event when user clicks on them.

The problem is to support resizing the UI. Interactive buildings should change their size along with the entire map and not change their location relative to the map.

I tried using Canvas. The map is displayed via Image. Interactive buildings are buttons with Image content. Unfortunately, with this implementation, I have to calculate the sizes and positions of all the images in my code and change them when the application window is resized.

Do you think there is a more convenient solution?

<UserControl <...>
             Bounds="{Binding CanvasBounds, Mode=OneWayToSource}">
    
    <Canvas VerticalAlignment="Stretch"
            HorizontalAlignment="Stretch"
            ClipToBounds="True">
        
        <!-- This is city map image -->
        <Image Canvas.Left="0"
               Canvas.Top="0"
               ZIndex="1"
               Stretch="Uniform"
               HorizontalAlignment="Stretch"
               VerticalAlignment="Stretch"
               IsEnabled="False"
               Width="{Binding MapImageWidth, Mode=OneWay}"
               Height="{Binding MapImageHeight, Mode=OneWay}"
               Source="avares://App/Assets/Images/Map0/map.png"/>
        
        <!-- This is button of interactive building -->
        <Button Canvas.Left="72"
                Canvas.Top="124"
                Classes="GameMapButton">
            <Image Source="avares://App/Assets/Images/Map0/big_office_0_normal.png"/>
            
            <Button.Styles>
                <Style Selector="Button:pointerover /template/ ContentPresenter#PART_ContentPresenter">
                    <Setter Property="Content">
                        <Template>
                            <Image Source="avares://App/Assets/Images/Map0/big_office_0_pointerover.png"/>
                        </Template>
                    </Setter>
                </Style>
            </Button.Styles>
            
            <Button.Styles>
                <Style Selector="Button:pressed /template/ ContentPresenter#PART_ContentPresenter">
                    <Setter Property="Content">
                        <Template>
                            <Image Source="avares://App/Assets/Images/Map0/big_office_0_pressed.png"/>
                        </Template>
                    </Setter>
                </Style>
            </Button.Styles>
        </Button>
        
        <...>
    </Canvas>
</UserControl>
1

There are 1 best solutions below

0
Oleg Olegovich On

I tried to use Viewbox in the root of UserControl. Since Canvas did not want to be displayed in this way, I replaced it with a regular Panel. The location of the buttons was set via Margin. I had to find all the offsets manually, restarting the application after each change. It was very tiring.

The solution seems to be working. When resizing the application window, the buttons do not shift relative to the map and change the size proportionally.

<UserControl ...>
<Viewbox Stretch="Uniform">
    <Panel>
        <Image ZIndex="1"
               IsEnabled="False"
               Source="avares://App/Assets/Images/Map0/map.png" />

        <Button Margin="73 -210 0 0"
                Classes="GameMapButton">
            <Image Source="avares://App/Assets/Images/Map0/big_office_0_normal.png" />

            <Button.Styles>
                <Style Selector="Button:pointerover /template/ ContentPresenter#PART_ContentPresenter">
                    <Setter Property="Content">
                        <Template>
                            <Image
                                Source="avares://App/Assets/Images/Map0/big_office_0_pointerover.png" />
                        </Template>
                    </Setter>
                </Style>
            </Button.Styles>

            <Button.Styles>
                <Style Selector="Button:pressed /template/ ContentPresenter#PART_ContentPresenter">
                    <Setter Property="Content">
                        <Template>
                            <Image Source="avares://App/Assets/Images/Map0/big_office_0_pressed.png" />
                        </Template>
                    </Setter>
                </Style>
            </Button.Styles>
        </Button>

        <Button Margin="720 -285 0 0"
                Classes="GameMapButton">
            <Image Source="avares://App/Assets/Images/Map0/middle_office_0_normal.png" />

            <Button.Styles>
                <Style Selector="Button:pointerover /template/ ContentPresenter#PART_ContentPresenter">
                    <Setter Property="Content">
                        <Template>
                            <Image
                                Source="avares://App/Assets/Images/Map0/middle_office_0_pointerover.png" />
                        </Template>
                    </Setter>
                </Style>
            </Button.Styles>

            <Button.Styles>
                <Style Selector="Button:pressed /template/ ContentPresenter#PART_ContentPresenter">
                    <Setter Property="Content">
                        <Template>
                            <Image
                                Source="avares://App/Assets/Images/Map0/middle_office_0_pressed.png" />
                        </Template>
                    </Setter>
                </Style>
            </Button.Styles>
        </Button>

        <...>
    </Panel>
</Viewbox>

It feels like writing code, the solution is stupid. I will be glad of alternative options.