From db457f52fae92f489863af52ded5c95615f3c5ed Mon Sep 17 00:00:00 2001
From: John Beard <john.j.beard@gmail.com>
Date: Wed, 24 Jul 2024 13:32:21 +0800
Subject: [PATCH] Move layer presentation logic to a separate class

The separates it from the LAYER_SELECTOR class - deciding
what color a layer is (say) is separate to managing the
actual selection of the layer. For example, sel_layer.cpp
only needs the presentation logic. This also makes it eaiser
to compose rather than inherit.

Additonally, break out the layer pair swatch function
to this class.

This will also be needed by the layer pair manager UI.

Relates-To: https://gitlab.com/kicad/code/kicad/-/issues/15227
---
 common/CMakeLists.txt                         |   1 +
 common/layer_presentation.cpp                 | 171 ++++++++++++++++++
 common/widgets/layer_box_selector.cpp         |  28 ---
 gerbview/widgets/gbr_layer_box_selector.cpp   |  68 ++++---
 gerbview/widgets/gbr_layer_box_selector.h     |  23 +--
 include/layer_presentation.h                  |  73 ++++++++
 include/widgets/layer_box_selector.h          |  13 --
 .../panel_board_stackup.cpp                   |   7 +-
 pcbnew/grid_layer_box_helpers.cpp             |  11 +-
 pcbnew/pcb_layer_box_selector.cpp             |  64 +++----
 pcbnew/pcb_layer_box_selector.h               |  26 +--
 pcbnew/pcb_layer_presentation.h               |  53 ++++++
 pcbnew/sel_layer.cpp                          |  98 +++++-----
 pcbnew/toolbars_pcb_editor.cpp                |  91 +---------
 14 files changed, 451 insertions(+), 276 deletions(-)
 create mode 100644 common/layer_presentation.cpp
 create mode 100644 include/layer_presentation.h
 create mode 100644 pcbnew/pcb_layer_presentation.h

diff --git a/common/CMakeLists.txt b/common/CMakeLists.txt
index 7be37ffcae..06aba33cb3 100644
--- a/common/CMakeLists.txt
+++ b/common/CMakeLists.txt
@@ -556,6 +556,7 @@ set( COMMON_SRCS
     hotkeys_basic.cpp
     kiface_base.cpp
     kiway_player.cpp
+    layer_presentation.cpp
     lib_table_grid_tricks.cpp
     lib_tree_model.cpp
     lib_tree_model_adapter.cpp
diff --git a/common/layer_presentation.cpp b/common/layer_presentation.cpp
new file mode 100644
index 0000000000..0f2a9bc2aa
--- /dev/null
+++ b/common/layer_presentation.cpp
@@ -0,0 +1,171 @@
+/*
+ * This program source code file is part of KiCad, a free EDA CAD application.
+ *
+ * Copyright (C) 2024 KiCad Developers, see AUTHORS.txt for contributors.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, you may find one here:
+ * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
+ * or you may search the http://www.gnu.org website for the version 2 license,
+ * or you may write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
+ */
+
+#include "layer_presentation.h"
+
+#include <wx/bitmap.h>
+#include <wx/dcmemory.h>
+
+#include <gal/color4d.h>
+
+
+using namespace KIGFX;
+
+
+void LAYER_PRESENTATION::DrawColorSwatch( wxBitmap& aLayerbmp, const COLOR4D& aBackground,
+                                      const COLOR4D& aColor )
+{
+    wxMemoryDC bmpDC;
+    wxBrush    brush;
+
+    // Prepare Bitmap
+    bmpDC.SelectObject( aLayerbmp );
+
+    brush.SetStyle( wxBRUSHSTYLE_SOLID );
+
+    if( aBackground != COLOR4D::UNSPECIFIED )
+    {
+        brush.SetColour( aBackground.WithAlpha( 1.0 ).ToColour() );
+        bmpDC.SetBrush( brush );
+        bmpDC.DrawRectangle( 0, 0, aLayerbmp.GetWidth(), aLayerbmp.GetHeight() );
+    }
+
+    brush.SetColour( aColor.ToColour() );
+    bmpDC.SetBrush( brush );
+    bmpDC.DrawRectangle( 0, 0, aLayerbmp.GetWidth(), aLayerbmp.GetHeight() );
+
+    bmpDC.SetBrush( *wxTRANSPARENT_BRUSH );
+    bmpDC.SetPen( *wxBLACK_PEN );
+    bmpDC.DrawRectangle( 0, 0, aLayerbmp.GetWidth(), aLayerbmp.GetHeight() );
+}
+
+
+void LAYER_PRESENTATION::DrawColorSwatch( wxBitmap& aLayerbmp, int aLayer ) const
+{
+    const COLOR4D bgColor = getLayerColor( LAYER_PCB_BACKGROUND );
+    const COLOR4D color = getLayerColor( aLayer );
+
+    DrawColorSwatch( aLayerbmp, bgColor, color );
+}
+
+
+static constexpr unsigned BM_LAYERICON_SIZE = 24;
+static const char         s_BitmapLayerIcon[BM_LAYERICON_SIZE][BM_LAYERICON_SIZE] = {
+    // 0 = draw pixel with white
+    // 1 = draw pixel with black
+    // 2 = draw pixel with top layer from router pair
+    // 3 = draw pixel with bottom layer from router pair
+    { 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 1, 0, 3, 3, 3, 3, 3 },
+    { 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 1, 0, 3, 3, 3, 3, 3 },
+    { 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 1, 0, 3, 3, 3, 3, 3, 3 },
+    { 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 1, 0, 3, 3, 3, 3, 3, 3 },
+    { 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 1, 0, 3, 3, 3, 3, 3, 3, 3 },
+    { 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 1, 0, 3, 3, 3, 3, 3, 3, 3 },
+    { 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 1, 0, 3, 3, 3, 3, 3, 3, 3, 3 },
+    { 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 1, 0, 3, 3, 3, 3, 3, 3, 3, 3 },
+    { 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 1, 0, 3, 3, 3, 3, 3, 3, 3, 3, 3 },
+    { 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 1, 0, 3, 3, 3, 3, 3, 3, 3, 3, 3 },
+    { 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 1, 0, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3 },
+    { 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 1, 0, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3 },
+    { 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 1, 0, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3 },
+    { 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 1, 0, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3 },
+    { 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 1, 0, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3 },
+    { 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 1, 0, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3 },
+    { 2, 2, 2, 2, 2, 2, 2, 2, 0, 1, 0, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3 },
+    { 2, 2, 2, 2, 2, 2, 2, 2, 0, 1, 0, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3 },
+    { 2, 2, 2, 2, 2, 2, 2, 0, 1, 0, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3 },
+    { 2, 2, 2, 2, 2, 2, 2, 0, 1, 0, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3 },
+    { 2, 2, 2, 2, 2, 2, 0, 1, 0, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3 },
+    { 2, 2, 2, 2, 2, 2, 0, 1, 0, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3 },
+    { 2, 2, 2, 2, 2, 0, 1, 0, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3 },
+    { 2, 2, 2, 2, 2, 0, 1, 0, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3 },
+};
+
+static COLOR4D ICON_WHITE{ 0.86, 0.86, 0.86, 1.0 };
+static COLOR4D ICON_BLACK{ 0.28, 0.28, 0.28, 1.0 };
+
+
+std::unique_ptr<wxBitmap> LAYER_PRESENTATION::CreateLayerPairIcon( const COLOR4D& aBgColor, const COLOR4D& aTopColor,
+                                               const COLOR4D& aBottomColor, int aScale )
+{
+    auto layerPairBitmap = std::make_unique<wxBitmap>( BM_LAYERICON_SIZE, BM_LAYERICON_SIZE );
+
+    // Draw the icon, with colors according to the router's layer pair
+    wxMemoryDC iconDC;
+    iconDC.SelectObject( *layerPairBitmap );
+    wxBrush brush;
+    wxPen   pen;
+    int     buttonColor = -1;
+
+    brush.SetStyle( wxBRUSHSTYLE_SOLID );
+    brush.SetColour( aBgColor.WithAlpha( 1.0 ).ToColour() );
+    iconDC.SetBrush( brush );
+    iconDC.DrawRectangle( 0, 0, BM_LAYERICON_SIZE, BM_LAYERICON_SIZE );
+
+    for( unsigned ii = 0; ii < BM_LAYERICON_SIZE; ii++ )
+    {
+        for( unsigned jj = 0; jj < BM_LAYERICON_SIZE; jj++ )
+        {
+            if( s_BitmapLayerIcon[ii][jj] != buttonColor )
+            {
+                switch( s_BitmapLayerIcon[ii][jj] )
+                {
+                default:
+                case 0: pen.SetColour( ICON_WHITE.ToColour() ); break;
+                case 1: pen.SetColour( ICON_BLACK.ToColour() ); break;
+                case 2: pen.SetColour( aTopColor.ToColour() ); break;
+                case 3: pen.SetColour( aBottomColor.ToColour() ); break;
+                }
+
+                buttonColor = s_BitmapLayerIcon[ii][jj];
+                iconDC.SetPen( pen );
+            }
+
+            iconDC.DrawPoint( jj, ii );
+        }
+    }
+
+    // Deselect the bitmap from the DC in order to delete the MemoryDC safely without
+    // deleting the bitmap
+    iconDC.SelectObject( wxNullBitmap );
+
+    // Scale the bitmap
+    wxImage image = layerPairBitmap->ConvertToImage();
+
+    // "NEAREST" causes less mixing of colors
+    image.Rescale( aScale * image.GetWidth() / 4, aScale * image.GetHeight() / 4,
+                   wxIMAGE_QUALITY_NEAREST );
+
+    return std::make_unique<wxBitmap>( image );
+}
+
+
+std::unique_ptr<wxBitmap> LAYER_PRESENTATION::CreateLayerPairIcon( const LAYER_PAIR& aPair,
+                                                                   int               aScale ) const
+{
+    const COLOR4D bgColor = getLayerColor( LAYER_PCB_BACKGROUND );
+    const COLOR4D topColor = getLayerColor( aPair.GetLayerA() );
+    const COLOR4D bottomColor = getLayerColor( aPair.GetLayerB() );
+
+    return CreateLayerPairIcon( bgColor, topColor, bottomColor, aScale );
+}
\ No newline at end of file
diff --git a/common/widgets/layer_box_selector.cpp b/common/widgets/layer_box_selector.cpp
index d99a07c618..b7450522e9 100644
--- a/common/widgets/layer_box_selector.cpp
+++ b/common/widgets/layer_box_selector.cpp
@@ -47,34 +47,6 @@ bool LAYER_SELECTOR::SetLayersHotkeys( bool value )
 }
 
 
-void LAYER_SELECTOR::DrawColorSwatch( wxBitmap& aLayerbmp, const COLOR4D& aBackground,
-                                      const COLOR4D& aColor )
-{
-    wxMemoryDC bmpDC;
-    wxBrush    brush;
-
-    // Prepare Bitmap
-    bmpDC.SelectObject( aLayerbmp );
-
-    brush.SetStyle( wxBRUSHSTYLE_SOLID );
-
-    if( aBackground != COLOR4D::UNSPECIFIED )
-    {
-        brush.SetColour( aBackground.WithAlpha( 1.0 ).ToColour() );
-        bmpDC.SetBrush( brush );
-        bmpDC.DrawRectangle( 0, 0, aLayerbmp.GetWidth(), aLayerbmp.GetHeight() );
-    }
-
-    brush.SetColour( aColor.ToColour() );
-    bmpDC.SetBrush( brush );
-    bmpDC.DrawRectangle( 0, 0, aLayerbmp.GetWidth(), aLayerbmp.GetHeight() );
-
-    bmpDC.SetBrush( *wxTRANSPARENT_BRUSH );
-    bmpDC.SetPen( *wxBLACK_PEN );
-    bmpDC.DrawRectangle( 0, 0, aLayerbmp.GetWidth(), aLayerbmp.GetHeight() );
-}
-
-
 LAYER_BOX_SELECTOR::LAYER_BOX_SELECTOR( wxWindow* parent, wxWindowID id, const wxPoint& pos,
                                         const wxSize& size, int n, const wxString choices[] ) :
         wxBitmapComboBox( parent, id, wxEmptyString, pos, size, n, choices, wxCB_READONLY ),
diff --git a/gerbview/widgets/gbr_layer_box_selector.cpp b/gerbview/widgets/gbr_layer_box_selector.cpp
index 89471c7231..f4ea864b7c 100644
--- a/gerbview/widgets/gbr_layer_box_selector.cpp
+++ b/gerbview/widgets/gbr_layer_box_selector.cpp
@@ -23,6 +23,10 @@
  * 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
  */
 
+#include "gbr_layer_box_selector.h"
+
+#include <layer_presentation.h>
+
 #include <gerbview_frame.h>
 #include <gerber_file_image_list.h>
 
@@ -30,7 +34,44 @@
 #include <dpi_scaling_common.h>
 #endif
 
-#include "gbr_layer_box_selector.h"
+
+/**
+ * Gerbview-specific implementation of the LAYER_PRESENTATION interface.
+ */
+class GBR_LAYER_PRESENTATION : public LAYER_PRESENTATION
+{
+public:
+    GBR_LAYER_PRESENTATION( GERBVIEW_FRAME& aFrame ) : m_frame( aFrame ) {}
+
+    // Returns a color index from the layer id
+    COLOR4D getLayerColor( int aLayer ) const override
+    {
+        return m_frame.GetLayerColor( GERBER_DRAW_LAYER( aLayer ) );
+    }
+
+    // Returns the name of the layer id
+    wxString getLayerName( int aLayer ) const override
+    {
+        GERBER_FILE_IMAGE_LIST& images = GERBER_FILE_IMAGE_LIST::GetImagesList();
+        wxString                name = images.GetDisplayName( aLayer );
+
+        return name;
+    }
+
+private:
+    GERBVIEW_FRAME& m_frame;
+};
+
+
+GBR_LAYER_BOX_SELECTOR::GBR_LAYER_BOX_SELECTOR( wxWindow* parent, wxWindowID id, const wxPoint& pos,
+                                                const wxSize& size, int n,
+                                                const wxString choices[] ) :
+        LAYER_BOX_SELECTOR( parent, id, pos, size, n, choices ),
+        m_layerPresentation( std::make_unique<GBR_LAYER_PRESENTATION>(
+                static_cast<GERBVIEW_FRAME&>( *parent->GetParent() ) ) )
+{
+    m_layerhotkeys = false;
+}
 
 void GBR_LAYER_BOX_SELECTOR::Resync()
 {
@@ -57,14 +98,14 @@ void GBR_LAYER_BOX_SELECTOR::Resync()
         {
             wxBitmap bmp( size * scale, size * scale );
 
-            DrawColorSwatch( bmp, getLayerColor( LAYER_PCB_BACKGROUND ), getLayerColor( layerid ) );
+            m_layerPresentation->DrawColorSwatch( bmp, layerid );
 
             bmp.SetScaleFactor( scale );
             bitmaps.push_back( bmp );
         }
 
-        Append( getLayerName( layerid ), wxBitmapBundle::FromBitmaps( bitmaps ),
-                (void*) (intptr_t) layerid );
+        Append( m_layerPresentation->getLayerName( layerid ),
+                wxBitmapBundle::FromBitmaps( bitmaps ), (void*) (intptr_t) layerid );
     }
 
     // Ensure the size of the widget is enough to show the text and the icon
@@ -88,22 +129,3 @@ void GBR_LAYER_BOX_SELECTOR::Resync()
 
     Thaw();
 }
-
-
-// Returns a color index from the layer id
-COLOR4D GBR_LAYER_BOX_SELECTOR::getLayerColor( int aLayer ) const
-{
-    GERBVIEW_FRAME* frame = (GERBVIEW_FRAME*) GetParent()->GetParent();
-
-    return frame->GetLayerColor( GERBER_DRAW_LAYER( aLayer ) );
-}
-
-
-// Returns the name of the layer id
-wxString GBR_LAYER_BOX_SELECTOR::getLayerName( int aLayer ) const
-{
-    GERBER_FILE_IMAGE_LIST& images = GERBER_FILE_IMAGE_LIST::GetImagesList();
-    wxString name = images.GetDisplayName( aLayer );
-
-    return name;
-}
diff --git a/gerbview/widgets/gbr_layer_box_selector.h b/gerbview/widgets/gbr_layer_box_selector.h
index 44dbba6a39..b1d2474a11 100644
--- a/gerbview/widgets/gbr_layer_box_selector.h
+++ b/gerbview/widgets/gbr_layer_box_selector.h
@@ -23,35 +23,30 @@
  */
 
 #ifndef GBR_LAYER_BOX_SELECTOR_H
-#define GBR_LAYER_BOX_SELECTOR_H 1
+#define GBR_LAYER_BOX_SELECTOR_H
+
+#include <memory>
 
 #include <widgets/layer_box_selector.h>
 
+class LAYER_PRESENTATION;
 
 // class to display a layer list in GerbView.
 class GBR_LAYER_BOX_SELECTOR : public LAYER_BOX_SELECTOR
 {
 public:
-    GBR_LAYER_BOX_SELECTOR( wxWindow* parent, wxWindowID id,
-                            const wxPoint& pos = wxDefaultPosition,
-                            const wxSize& size = wxDefaultSize,
-                            int n = 0, const wxString choices[] = nullptr ) :
-        LAYER_BOX_SELECTOR( parent, id, pos, size, n, choices )
-    {
-        m_layerhotkeys = false;
-    }
+    GBR_LAYER_BOX_SELECTOR( wxWindow* parent, wxWindowID id, const wxPoint& pos = wxDefaultPosition,
+                            const wxSize& size = wxDefaultSize, int n = 0,
+                            const wxString choices[] = nullptr );
 
     // Reload the Layers names and bitmaps
     void Resync() override;
 
-    // Return a color index from the layer id
-    COLOR4D getLayerColor( int aLayer ) const override;
-
     // Return true if the layer id is enabled (i.e. is it should be displayed)
     bool isLayerEnabled( int aLayer ) const override { return true; }
 
-    // Return the name of the layer id
-    wxString getLayerName( int aLayer ) const override;
+private:
+    std::unique_ptr<LAYER_PRESENTATION> m_layerPresentation;
 };
 
 #endif //GBR_LAYER_BOX_SELECTOR_H
diff --git a/include/layer_presentation.h b/include/layer_presentation.h
new file mode 100644
index 0000000000..9dc208152c
--- /dev/null
+++ b/include/layer_presentation.h
@@ -0,0 +1,73 @@
+/*
+ * This program source code file is part of KiCad, a free EDA CAD application.
+ *
+ * Copyright (C) 2024 KiCad Developers, see AUTHORS.txt for contributors.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, you may find one here:
+ * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
+ * or you may search the http://www.gnu.org website for the version 2 license,
+ * or you may write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
+ */
+
+#ifndef LAYER_PRESENTATION_H
+#define LAYER_PRESENTATION_H
+
+#include <gal/color4d.h>
+#include <layer_ids.h>
+#include <layer_pairs.h>
+
+class wxBitmap;
+
+using KIGFX::COLOR4D;
+
+/**
+ * Base class for an object that can provide information about
+ * presenting layers (colours, etc).
+ */
+class LAYER_PRESENTATION
+{
+public:
+    // Return a color index from the layer id
+    virtual COLOR4D getLayerColor( int aLayer ) const = 0;
+
+    // Return the name of the layer id
+    virtual wxString getLayerName( int aLayer ) const = 0;
+
+    // Fill the layer bitmap aLayerbmp with the layer color
+    static void DrawColorSwatch( wxBitmap& aLayerbmp, const COLOR4D& aBackground,
+                                 const COLOR4D& aColor );
+
+    /**
+     * Fill the layer bitmap aLayerbmp with the layer color
+     * for the layer ID.
+     */
+    void DrawColorSwatch( wxBitmap& aLayerbmp, int aLayer ) const;
+
+    /**
+     * Create a layer pair "side-by-side swatch" icon
+     */
+    static std::unique_ptr<wxBitmap> CreateLayerPairIcon( const KIGFX::COLOR4D& aBgColor,
+                                                          const KIGFX::COLOR4D& aTopColor,
+                                                          const KIGFX::COLOR4D& aBottomColor,
+                                                          int                   aScale );
+
+    /**
+     * Create a layer pair "side-by-side swatch" icon for the given
+     * layer pair with the style of this presentation.
+     */
+    std::unique_ptr<wxBitmap> CreateLayerPairIcon( const LAYER_PAIR& aPair, int aScale ) const;
+};
+
+#endif // LAYER_PRESENTATION_H
\ No newline at end of file
diff --git a/include/widgets/layer_box_selector.h b/include/widgets/layer_box_selector.h
index e275bd1bcc..2f1849ff01 100644
--- a/include/widgets/layer_box_selector.h
+++ b/include/widgets/layer_box_selector.h
@@ -26,10 +26,7 @@
 #define LAYER_BOX_SELECTOR_H
 
 #include <wx/bmpcbox.h>
-#include <gal/color4d.h>
-#include <layer_ids.h>
 
-using KIGFX::COLOR4D;
 
 /**
  * Base class to build a layer list.
@@ -43,17 +40,7 @@ public:
 
     bool SetLayersHotkeys( bool value );
 
-    // Fill the layer bitmap aLayerbmp with the layer color
-    static void DrawColorSwatch( wxBitmap& aLayerbmp, const COLOR4D& aBackground,
-                                 const COLOR4D& aColor );
-
 protected:
-    // Return a color index from the layer id
-    virtual COLOR4D getLayerColor( int aLayer ) const = 0;
-
-    // Return the name of the layer id
-    virtual wxString getLayerName( int aLayer ) const = 0;
-
     // Return true if the layer id is enabled (i.e. is it should be displayed)
     virtual bool isLayerEnabled( int aLayer ) const = 0;
 
diff --git a/pcbnew/board_stackup_manager/panel_board_stackup.cpp b/pcbnew/board_stackup_manager/panel_board_stackup.cpp
index 9e96dc1b58..ef7d0022ad 100644
--- a/pcbnew/board_stackup_manager/panel_board_stackup.cpp
+++ b/pcbnew/board_stackup_manager/panel_board_stackup.cpp
@@ -26,6 +26,7 @@
 #include <pcb_edit_frame.h>
 #include <board.h>
 #include <board_design_settings.h>
+#include <layer_presentation.h>
 #include <dialogs/dialog_color_picker.h>
 #include <widgets/paged_dialog.h>
 #include <widgets/layer_box_selector.h>
@@ -684,7 +685,7 @@ void PANEL_SETUP_BOARD_STACKUP::synchronizeWithBoard( bool aFullSync )
                 {
                     bm_combo->SetString( selected, item->GetColor( sub_item ) );
                     wxBitmap layerbmp( m_colorSwatchesSize.x, m_colorSwatchesSize.y );
-                    LAYER_SELECTOR::DrawColorSwatch( layerbmp, COLOR4D(), custom_color );
+                    LAYER_PRESENTATION::DrawColorSwatch( layerbmp, COLOR4D(), custom_color );
                     bm_combo->SetItemBitmap( selected, layerbmp );
                 }
             }
@@ -1404,7 +1405,7 @@ void PANEL_SETUP_BOARD_STACKUP::onColorSelected( wxCommandEvent& event )
             combo->SetString( idx, color.ToHexString() );
 
             wxBitmap layerbmp( m_colorSwatchesSize.x, m_colorSwatchesSize.y );
-            LAYER_SELECTOR::DrawColorSwatch( layerbmp, COLOR4D( 0, 0, 0, 0 ), color );
+            LAYER_PRESENTATION::DrawColorSwatch( layerbmp, COLOR4D( 0, 0, 0, 0 ), color );
             combo->SetItemBitmap( combo->GetCount() - 1, layerbmp );
 
             combo->SetSelection( idx );
@@ -1654,7 +1655,7 @@ wxBitmapComboBox* PANEL_SETUP_BOARD_STACKUP::createColorBox( BOARD_STACKUP_ITEM*
         }
 
         wxBitmap layerbmp( m_colorSwatchesSize.x, m_colorSwatchesSize.y );
-        LAYER_SELECTOR::DrawColorSwatch( layerbmp, COLOR4D( 0, 0, 0, 0 ), curr_color );
+        LAYER_PRESENTATION::DrawColorSwatch( layerbmp, COLOR4D( 0, 0, 0, 0 ), curr_color );
 
         combo->Append( label, layerbmp );
     }
diff --git a/pcbnew/grid_layer_box_helpers.cpp b/pcbnew/grid_layer_box_helpers.cpp
index 57cfd5c278..30ee18ad34 100644
--- a/pcbnew/grid_layer_box_helpers.cpp
+++ b/pcbnew/grid_layer_box_helpers.cpp
@@ -22,17 +22,20 @@
  */
 
 #include <grid_layer_box_helpers.h>
+
+#include <wx/textctrl.h>
+
 #include <pgm_base.h>
 #include <settings/settings_manager.h>
 #include <settings/color_settings.h>
 #include <footprint_editor_settings.h>
 #include <board.h>
+#include <layer_presentation.h>
 #include <lset.h>
 #include <pcb_edit_frame.h>
 #include <pcb_layer_box_selector.h>
 #include <settings/color_settings.h>
 #include <widgets/layer_box_selector.h>
-#include <wx/textctrl.h>
 
 
 //-------- Custom wxGridCellRenderers --------------------------------------------------
@@ -76,9 +79,9 @@ void GRID_CELL_LAYER_RENDERER::Draw( wxGrid& aGrid, wxGridCellAttr& aAttr, wxDC&
     int      size = KiROUND( 14 * aDC.GetContentScaleFactor() );
     wxBitmap bitmap( size, size );
 
-    LAYER_SELECTOR::DrawColorSwatch( bitmap,
-                                     cs->GetColor( ToLAYER_ID( LAYER_PCB_BACKGROUND ) ),
-                                     cs->GetColor( ToLAYER_ID( value ) ) );
+    LAYER_PRESENTATION::DrawColorSwatch( bitmap,
+                                         cs->GetColor( ToLAYER_ID( LAYER_PCB_BACKGROUND ) ),
+                                         cs->GetColor( ToLAYER_ID( value ) ) );
 
     aDC.DrawBitmap( bitmap, rect.GetLeft() + 4,
                     rect.GetTop() + ( rect.GetHeight() - bitmap.GetHeight() ) / 2, true );
diff --git a/pcbnew/pcb_layer_box_selector.cpp b/pcbnew/pcb_layer_box_selector.cpp
index fd17b07292..ac831a0598 100644
--- a/pcbnew/pcb_layer_box_selector.cpp
+++ b/pcbnew/pcb_layer_box_selector.cpp
@@ -23,19 +23,35 @@
  * 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
  */
 
-#include <pgm_base.h>
-#include <settings/settings_manager.h>
-#include <footprint_editor_settings.h>
-#include <pcb_edit_frame.h>
-#include <layer_ids.h>
-#include <settings/color_settings.h>
+#include "pcb_layer_box_selector.h"
+
 #include <board.h>
-#include <pcb_layer_box_selector.h>
+#include <layer_ids.h>
+#include <pcb_edit_frame.h>
+#include <pcb_layer_presentation.h>
+#include <settings/color_settings.h>
 #include <tools/pcb_actions.h>
 #include <dpi_scaling_common.h>
 
 
-// class to display a layer list in a wxBitmapComboBox.
+PCB_LAYER_BOX_SELECTOR::PCB_LAYER_BOX_SELECTOR( wxWindow* parent, wxWindowID id,
+                                                const wxString& value, const wxPoint& pos,
+                                                const wxSize& size, int n, const wxString choices[],
+                                                int style ) :
+        LAYER_BOX_SELECTOR( parent, id, pos, size, n, choices ), m_boardFrame( nullptr ),
+        m_showNotEnabledBrdlayers( false ),
+        m_layerPresentation( std::make_unique<PCB_LAYER_PRESENTATION>(
+                nullptr ) ) // The parent isn't awlays the frame
+{
+}
+
+
+void PCB_LAYER_BOX_SELECTOR::SetBoardFrame( PCB_BASE_FRAME* aFrame )
+{
+    m_boardFrame = aFrame;
+    m_layerPresentation->SetBoardFrame( m_boardFrame );
+}
+
 
 // Reload the Layers
 void PCB_LAYER_BOX_SELECTOR::Resync()
@@ -64,13 +80,13 @@ void PCB_LAYER_BOX_SELECTOR::Resync()
         {
             wxBitmap bmp( size * scale, size * scale );
 
-            DrawColorSwatch( bmp, getLayerColor( LAYER_PCB_BACKGROUND ), getLayerColor( layerid ) );
+            m_layerPresentation->DrawColorSwatch( bmp, layerid );
 
             bmp.SetScaleFactor( scale );
             bitmaps.push_back( bmp );
         }
 
-        wxString layername = getLayerName( layerid ) + layerstatus;
+        wxString layername = m_layerPresentation->getLayerName( layerid ) + layerstatus;
 
         if( m_layerhotkeys )
         {
@@ -122,31 +138,3 @@ LSET PCB_LAYER_BOX_SELECTOR::getEnabledLayers() const
     else
         return footprintEditorLayers;
 }
-
-
-// Returns a color index from the layer id
-COLOR4D PCB_LAYER_BOX_SELECTOR::getLayerColor( int aLayer ) const
-{
-    if( m_boardFrame )
-    {
-        return m_boardFrame->GetColorSettings()->GetColor( aLayer );
-    }
-    else
-    {
-        SETTINGS_MANAGER&          mgr = Pgm().GetSettingsManager();
-        FOOTPRINT_EDITOR_SETTINGS* settings = mgr.GetAppSettings<FOOTPRINT_EDITOR_SETTINGS>();
-        COLOR_SETTINGS*            current  = mgr.GetColorSettings( settings->m_ColorTheme );
-
-        return current->GetColor( aLayer );
-    }
-}
-
-
-// Returns the name of the layer id
-wxString PCB_LAYER_BOX_SELECTOR::getLayerName( int aLayer ) const
-{
-    if( m_boardFrame )
-        return m_boardFrame->GetBoard()->GetLayerName( ToLAYER_ID( aLayer ) );
-    else
-        return BOARD::GetStandardLayerName( ToLAYER_ID( aLayer ) );
-}
diff --git a/pcbnew/pcb_layer_box_selector.h b/pcbnew/pcb_layer_box_selector.h
index e9ce2c9b7c..0c58828cb7 100644
--- a/pcbnew/pcb_layer_box_selector.h
+++ b/pcbnew/pcb_layer_box_selector.h
@@ -25,10 +25,14 @@
 #ifndef PCB_LAYER_BOX_SELECTOR_H
 #define PCB_LAYER_BOX_SELECTOR_H
 
+#include <memory>
+
 #include <lset.h>
 #include <widgets/layer_box_selector.h>
 
 class PCB_BASE_FRAME;
+class PCB_LAYER_PRESENTATION;
+
 
 /**
  * Class to display a pcb layer list in a wxBitmapComboBox.
@@ -39,21 +43,15 @@ public:
     // If you are thinking the constructor is a bit curious, just remember it is automatically
     // generated when used in wxFormBuilder files, and so must have the same signature as the
     // wxBitmapComboBox constructor.  In particular, value and style are not used by this class.
-    PCB_LAYER_BOX_SELECTOR( wxWindow* parent, wxWindowID id,
-                            const wxString& value = wxEmptyString,
+    PCB_LAYER_BOX_SELECTOR( wxWindow* parent, wxWindowID id, const wxString& value = wxEmptyString,
                             const wxPoint& pos = wxDefaultPosition,
-                            const wxSize& size = wxDefaultSize,
-                            int n = 0, const wxString choices[] = nullptr, int style = 0 ) :
-        LAYER_BOX_SELECTOR( parent, id, pos, size, n, choices )
-    {
-        m_boardFrame = nullptr;
-        m_showNotEnabledBrdlayers = false;
-    }
+                            const wxSize& size = wxDefaultSize, int n = 0,
+                            const wxString choices[] = nullptr, int style = 0 );
 
     // SetBoardFrame should be called after creating a PCB_LAYER_BOX_SELECTOR.  It is not passed
     // through the constructor because it must have the same signature as wxBitmapComboBox for
     // use with wxFormBuilder.
-    void SetBoardFrame( PCB_BASE_FRAME* aFrame ) { m_boardFrame = aFrame; };
+    void SetBoardFrame( PCB_BASE_FRAME* aFrame );
 
     // SetLayerSet allows disabling some layers, which are not shown in list
     void SetNotAllowedLayerSet( LSET aMask ) { m_layerMaskDisable = aMask; }
@@ -70,15 +68,9 @@ public:
     void ShowNonActivatedLayers( bool aShow ) { m_showNotEnabledBrdlayers = aShow; }
 
 private:
-    // Returns a color index from the layer id
-    COLOR4D getLayerColor( int aLayer ) const override;
-
     // Returns true if the layer id is enabled (i.e. if it should be displayed)
     bool isLayerEnabled( int aLayer ) const override;
 
-    // Returns the name of the layer id
-    wxString getLayerName( int aLayer ) const override;
-
     LSET getEnabledLayers() const;
 
     PCB_BASE_FRAME* m_boardFrame;
@@ -89,6 +81,8 @@ private:
                                         // (with not activated layers flagged)
     wxString m_undefinedLayerName;      // if not empty add an item with this name which sets
                                         // the layer to UNDEFINED_LAYER
+
+    std::unique_ptr<PCB_LAYER_PRESENTATION> m_layerPresentation;
 };
 
 #endif // PCB_LAYER_BOX_SELECTOR_H
diff --git a/pcbnew/pcb_layer_presentation.h b/pcbnew/pcb_layer_presentation.h
new file mode 100644
index 0000000000..4b39de422d
--- /dev/null
+++ b/pcbnew/pcb_layer_presentation.h
@@ -0,0 +1,53 @@
+/*
+ * This program source code file is part of KiCad, a free EDA CAD application.
+ *
+ * Copyright (C) 2024 KiCad Developers, see AUTHORS.txt for contributors.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, you may find one here:
+ * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
+ * or you may search the http://www.gnu.org website for the version 2 license,
+ * or you may write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
+ */
+
+#ifndef PCB_LAYER_PRESENTATION_H
+#define PCB_LAYER_PRESENTATION_H
+
+#include <layer_presentation.h>
+
+class PCB_BASE_FRAME;
+
+/**
+ * Class that manages the presentation of PCB layers in a PCB frame.
+ */
+class PCB_LAYER_PRESENTATION : public LAYER_PRESENTATION
+{
+public:
+    PCB_LAYER_PRESENTATION( PCB_BASE_FRAME* aFrame );
+
+    COLOR4D getLayerColor( int aLayer ) const override;
+
+    wxString getLayerName( int aLayer ) const override;
+
+    /**
+     * Annoying post-ctor initialization (for when PCB_LAYER_BOX_SELECTOR doesn't
+     * have access to the PCB_BASE_FRAME at construction time).
+     */
+    void SetBoardFrame( PCB_BASE_FRAME* aFrame ) { m_boardFrame = aFrame; }
+
+private:
+    PCB_BASE_FRAME* m_boardFrame;
+};
+
+#endif // PCB_LAYER_PRESENTATION_H
\ No newline at end of file
diff --git a/pcbnew/sel_layer.cpp b/pcbnew/sel_layer.cpp
index 48ca2e1774..ff9946af35 100644
--- a/pcbnew/sel_layer.cpp
+++ b/pcbnew/sel_layer.cpp
@@ -26,11 +26,14 @@
 #include <kiplatform/ui.h>
 #include <confirm.h>
 #include <lset.h>
-#include <pcb_base_frame.h>
-#include <widgets/layer_box_selector.h>
 #include <board.h>
+#include <pgm_base.h>
+#include <pcb_base_frame.h>
+#include <pcb_layer_presentation.h>
+#include <footprint_editor_settings.h>
 #include <dialogs/dialog_layer_selection_base.h>
 #include <router/router_tool.h>
+#include <settings/settings_manager.h>
 #include <settings/color_settings.h>
 #include <tools/pcb_actions.h>
 
@@ -40,45 +43,40 @@
 #define LAYERNAME_COLNUM    2
 #define LAYER_HK_COLUMN     3
 
-/*
- * Display a layer list using a wxGrid.
- */
-class PCB_LAYER_SELECTOR: public LAYER_SELECTOR
+
+PCB_LAYER_PRESENTATION::PCB_LAYER_PRESENTATION( PCB_BASE_FRAME* aFrame ) : m_boardFrame( aFrame )
 {
-public:
-    PCB_LAYER_SELECTOR( PCB_BASE_FRAME* aFrame ) :
-        LAYER_SELECTOR()
-    {
-        m_frame = aFrame;
-    }
+}
 
-protected:
-    PCB_BASE_FRAME*  m_frame;
-
-    ///< @return true if the layer id is enabled (i.e. is it should be displayed).
-    bool isLayerEnabled( int aLayer ) const override
+COLOR4D PCB_LAYER_PRESENTATION::getLayerColor( int aLayer ) const
+{
+    if( m_boardFrame )
     {
-        return m_frame->GetBoard()->IsLayerEnabled( PCB_LAYER_ID( aLayer ) );
+        return m_boardFrame->GetColorSettings()->GetColor( aLayer );
     }
-
-    // Return the color index from the layer ID.
-    COLOR4D getLayerColor( int aLayer ) const override
+    else
     {
-        return m_frame->GetColorSettings()->GetColor( aLayer );
-    }
+        SETTINGS_MANAGER&          mgr = Pgm().GetSettingsManager();
+        FOOTPRINT_EDITOR_SETTINGS* settings = mgr.GetAppSettings<FOOTPRINT_EDITOR_SETTINGS>();
+        COLOR_SETTINGS*            current = mgr.GetColorSettings( settings->m_ColorTheme );
 
-    // Return the name of the layer ID.
-    wxString getLayerName( int aLayer ) const override
-    {
-        return m_frame->GetBoard()->GetLayerName( ToLAYER_ID( aLayer ) );
+        return current->GetColor( aLayer );
     }
-};
+}
+
+wxString PCB_LAYER_PRESENTATION::getLayerName( int aLayer ) const
+{
+    if( m_boardFrame )
+        return m_boardFrame->GetBoard()->GetLayerName( ToLAYER_ID( aLayer ) );
+    else
+        return BOARD::GetStandardLayerName( ToLAYER_ID( aLayer ) );
+}
 
 
 /**
  * Display a PCB layers list in a dialog to select one layer from this list.
  */
-class PCB_ONE_LAYER_SELECTOR : public PCB_LAYER_SELECTOR, public DIALOG_LAYER_SELECTION_BASE
+class PCB_ONE_LAYER_SELECTOR : public DIALOG_LAYER_SELECTION_BASE
 {
 public:
     PCB_ONE_LAYER_SELECTOR( PCB_BASE_FRAME* aParent, BOARD * aBrd, PCB_LAYER_ID aDefaultLayer,
@@ -104,6 +102,7 @@ private:
 
     void buildList();
 
+    PCB_LAYER_PRESENTATION    m_layerPresentation;
     PCB_LAYER_ID              m_layerSelected;
     LSET                      m_notAllowedLayersMask;
     BOARD*                    m_brd;
@@ -114,10 +113,8 @@ private:
 
 PCB_ONE_LAYER_SELECTOR::PCB_ONE_LAYER_SELECTOR( PCB_BASE_FRAME* aParent, BOARD* aBrd,
                                                 PCB_LAYER_ID aDefaultLayer,
-                                                LSET aNotAllowedLayersMask,
-                                                bool aHideCheckBoxes ) :
-        PCB_LAYER_SELECTOR( aParent ),
-        DIALOG_LAYER_SELECTION_BASE( aParent )
+                                                LSET aNotAllowedLayersMask, bool aHideCheckBoxes ) :
+        DIALOG_LAYER_SELECTION_BASE( aParent ), m_layerPresentation( aParent )
 {
     m_useCalculatedSize = true;
 
@@ -198,7 +195,7 @@ void PCB_ONE_LAYER_SELECTOR::onCharHook( wxKeyEvent& event )
 
 void PCB_ONE_LAYER_SELECTOR::buildList()
 {
-    wxColour bg = getLayerColor( LAYER_PCB_BACKGROUND ).ToColour();
+    wxColour bg = m_layerPresentation.getLayerColor( LAYER_PCB_BACKGROUND ).ToColour();
     int      left_row = 0;
     int      right_row = 0;
     wxString layername;
@@ -208,12 +205,12 @@ void PCB_ONE_LAYER_SELECTOR::buildList()
         if( m_notAllowedLayersMask[layerid] )
             continue;
 
-        wxColour fg = getLayerColor( layerid ).ToColour();
+        wxColour fg = m_layerPresentation.getLayerColor( layerid ).ToColour();
         wxColour color( wxColour::AlphaBlend( fg.Red(), bg.Red(), fg.Alpha() / 255.0 ),
                         wxColour::AlphaBlend( fg.Green(), bg.Green(), fg.Alpha() / 255.0 ),
                         wxColour::AlphaBlend( fg.Blue(), bg.Blue(), fg.Alpha() / 255.0 ) );
 
-        layername = wxT( " " ) + getLayerName( layerid );
+        layername = wxT( " " ) + m_layerPresentation.getLayerName( layerid );
 
         if( IsCopperLayer( layerid ) )
         {
@@ -304,8 +301,7 @@ PCB_LAYER_ID PCB_BASE_FRAME::SelectOneLayer( PCB_LAYER_ID aDefaultLayer, LSET aN
 /**
  * Display a pair PCB copper layers list in a dialog to select a layer pair from these lists.
  */
-class SELECT_COPPER_LAYERS_PAIR_DIALOG: public PCB_LAYER_SELECTOR,
-                                        public DIALOG_COPPER_LAYER_PAIR_SELECTION_BASE
+class SELECT_COPPER_LAYERS_PAIR_DIALOG : public DIALOG_COPPER_LAYER_PAIR_SELECTION_BASE
 {
 public:
     SELECT_COPPER_LAYERS_PAIR_DIALOG( PCB_BASE_FRAME* aParent, BOARD* aPcb,
@@ -323,11 +319,12 @@ private:
 
     void buildList();
 
-    BOARD*       m_brd;
-    PCB_LAYER_ID m_frontLayer;
-    PCB_LAYER_ID m_backLayer;
-    int          m_leftRowSelected;
-    int          m_rightRowSelected;
+    PCB_LAYER_PRESENTATION m_layerPresentation;
+    BOARD*                 m_brd;
+    PCB_LAYER_ID           m_frontLayer;
+    PCB_LAYER_ID           m_backLayer;
+    int                    m_leftRowSelected;
+    int                    m_rightRowSelected;
 
     std::vector<PCB_LAYER_ID> m_layersId;
 };
@@ -354,10 +351,11 @@ int ROUTER_TOOL::SelectCopperLayerPair( const TOOL_EVENT& aEvent )
 }
 
 
-SELECT_COPPER_LAYERS_PAIR_DIALOG::SELECT_COPPER_LAYERS_PAIR_DIALOG(
-        PCB_BASE_FRAME* aParent, BOARD * aPcb, PCB_LAYER_ID aFrontLayer, PCB_LAYER_ID aBackLayer) :
-    PCB_LAYER_SELECTOR( aParent ),
-    DIALOG_COPPER_LAYER_PAIR_SELECTION_BASE( aParent )
+SELECT_COPPER_LAYERS_PAIR_DIALOG::SELECT_COPPER_LAYERS_PAIR_DIALOG( PCB_BASE_FRAME* aParent,
+                                                                    BOARD*          aPcb,
+                                                                    PCB_LAYER_ID    aFrontLayer,
+                                                                    PCB_LAYER_ID    aBackLayer ) :
+        DIALOG_COPPER_LAYER_PAIR_SELECTION_BASE( aParent ), m_layerPresentation( aParent )
 {
     m_frontLayer = aFrontLayer;
     m_backLayer = aBackLayer;
@@ -380,7 +378,7 @@ SELECT_COPPER_LAYERS_PAIR_DIALOG::SELECT_COPPER_LAYERS_PAIR_DIALOG(
 
 void SELECT_COPPER_LAYERS_PAIR_DIALOG::buildList()
 {
-    wxColour bg = getLayerColor( LAYER_PCB_BACKGROUND ).ToColour();
+    wxColour bg = m_layerPresentation.getLayerColor( LAYER_PCB_BACKGROUND ).ToColour();
     int      row = 0;
     wxString layername;
 
@@ -389,12 +387,12 @@ void SELECT_COPPER_LAYERS_PAIR_DIALOG::buildList()
         if( !IsCopperLayer( layerid ) )
             continue;
 
-        wxColour fg = getLayerColor( layerid ).ToColour();
+        wxColour fg = m_layerPresentation.getLayerColor( layerid ).ToColour();
         wxColour color( wxColour::AlphaBlend( fg.Red(), bg.Red(), fg.Alpha() / 255.0 ),
                         wxColour::AlphaBlend( fg.Green(), bg.Green(), fg.Alpha() / 255.0 ),
                         wxColour::AlphaBlend( fg.Blue(), bg.Blue(), fg.Alpha() / 255.0 ) );
 
-        layername = wxT( " " ) + getLayerName( layerid );
+        layername = wxT( " " ) + m_layerPresentation.getLayerName( layerid );
 
         if( row )
             m_leftGridLayers->AppendRows( 1 );
diff --git a/pcbnew/toolbars_pcb_editor.cpp b/pcbnew/toolbars_pcb_editor.cpp
index db50672647..ee816bd853 100644
--- a/pcbnew/toolbars_pcb_editor.cpp
+++ b/pcbnew/toolbars_pcb_editor.cpp
@@ -33,6 +33,7 @@
 #include <board_design_settings.h>
 #include <kiface_base.h>
 #include <kiplatform/ui.h>
+#include <layer_presentation.h>
 #include <macros.h>
 #include <pcb_edit_frame.h>
 #include <pcb_layer_box_selector.h>
@@ -49,12 +50,12 @@
 #include <tools/pcb_actions.h>
 #include <tools/pcb_selection_tool.h>
 #include <widgets/appearance_controls.h>
+#include <widgets/layer_box_selector.h>
 #include <widgets/pcb_properties_panel.h>
 #include <widgets/net_inspector_panel.h>
 #include <widgets/pcb_search_pane.h>
 #include <widgets/wx_aui_utils.h>
 #include <wx/wupdlock.h>
-#include <wx/dcmemory.h>
 #include <wx/combobox.h>
 
 #include "../scripting/python_scripting.h"
@@ -63,46 +64,9 @@
 /* Data to build the layer pair indicator button */
 static std::unique_ptr<wxBitmap> LayerPairBitmap;
 
-#define BM_LAYERICON_SIZE 24
-static const char s_BitmapLayerIcon[BM_LAYERICON_SIZE][BM_LAYERICON_SIZE] =
-{
-    // 0 = draw pixel with white
-    // 1 = draw pixel with black
-    // 2 = draw pixel with top layer from router pair
-    // 3 = draw pixel with bottom layer from router pair
-    { 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 1, 0, 3, 3, 3, 3, 3 },
-    { 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 1, 0, 3, 3, 3, 3, 3 },
-    { 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 1, 0, 3, 3, 3, 3, 3, 3 },
-    { 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 1, 0, 3, 3, 3, 3, 3, 3 },
-    { 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 1, 0, 3, 3, 3, 3, 3, 3, 3 },
-    { 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 1, 0, 3, 3, 3, 3, 3, 3, 3 },
-    { 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 1, 0, 3, 3, 3, 3, 3, 3, 3, 3 },
-    { 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 1, 0, 3, 3, 3, 3, 3, 3, 3, 3 },
-    { 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 1, 0, 3, 3, 3, 3, 3, 3, 3, 3, 3 },
-    { 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 1, 0, 3, 3, 3, 3, 3, 3, 3, 3, 3 },
-    { 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 1, 0, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3 },
-    { 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 1, 0, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3 },
-    { 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 1, 0, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3 },
-    { 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 1, 0, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3 },
-    { 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 1, 0, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3 },
-    { 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 1, 0, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3 },
-    { 2, 2, 2, 2, 2, 2, 2, 2, 0, 1, 0, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3 },
-    { 2, 2, 2, 2, 2, 2, 2, 2, 0, 1, 0, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3 },
-    { 2, 2, 2, 2, 2, 2, 2, 0, 1, 0, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3 },
-    { 2, 2, 2, 2, 2, 2, 2, 0, 1, 0, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3 },
-    { 2, 2, 2, 2, 2, 2, 0, 1, 0, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3 },
-    { 2, 2, 2, 2, 2, 2, 0, 1, 0, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3 },
-    { 2, 2, 2, 2, 2, 0, 1, 0, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3 },
-    { 2, 2, 2, 2, 2, 0, 1, 0, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3 },
-};
-
-static COLOR4D ICON_WHITE { 0.86, 0.86, 0.86, 1.0 };
-static COLOR4D ICON_BLACK { 0.28, 0.28, 0.28, 1.0 };
-
 
 void PCB_EDIT_FRAME::PrepareLayerIndicator( bool aForceRebuild )
 {
-    int        ii, jj;
     COLOR4D    top_color, bottom_color, background_color;
     bool       change = aForceRebuild;
 
@@ -140,56 +104,9 @@ void PCB_EDIT_FRAME::PrepareLayerIndicator( bool aForceRebuild )
 
     if( change || !LayerPairBitmap )
     {
-        LayerPairBitmap = std::make_unique<wxBitmap>( 24, 24 );
-
-        // Draw the icon, with colors according to the router's layer pair
-        wxMemoryDC iconDC;
-        iconDC.SelectObject( *LayerPairBitmap );
-        wxBrush    brush;
-        wxPen      pen;
-        int buttonColor = -1;
-
-        brush.SetStyle( wxBRUSHSTYLE_SOLID );
-        brush.SetColour( background_color.WithAlpha(1.0).ToColour() );
-        iconDC.SetBrush( brush );
-        iconDC.DrawRectangle( 0, 0, BM_LAYERICON_SIZE, BM_LAYERICON_SIZE );
-
-        for( ii = 0; ii < BM_LAYERICON_SIZE; ii++ )
-        {
-            for( jj = 0; jj < BM_LAYERICON_SIZE; jj++ )
-            {
-                if( s_BitmapLayerIcon[ii][jj] != buttonColor )
-                {
-                    switch( s_BitmapLayerIcon[ii][jj] )
-                    {
-                    default:
-                    case 0: pen.SetColour( ICON_WHITE.ToColour() );   break;
-                    case 1: pen.SetColour( ICON_BLACK.ToColour() );   break;
-                    case 2: pen.SetColour( top_color.ToColour() );    break;
-                    case 3: pen.SetColour( bottom_color.ToColour() ); break;
-                    }
-
-                    buttonColor = s_BitmapLayerIcon[ii][jj];
-                    iconDC.SetPen( pen );
-                }
-
-                iconDC.DrawPoint( jj, ii );
-            }
-        }
-
-        // Deselect the bitmap from the DC in order to delete the MemoryDC safely without
-        // deleting the bitmap
-        iconDC.SelectObject( wxNullBitmap );
-
-        // Scale the bitmap
         const int scale = ( requested_scale <= 0 ) ? KiIconScale( this ) : requested_scale;
-        wxImage image = LayerPairBitmap->ConvertToImage();
-
-        // "NEAREST" causes less mixing of colors
-        image.Rescale( scale * image.GetWidth() / 4, scale * image.GetHeight() / 4,
-                       wxIMAGE_QUALITY_NEAREST );
-
-        LayerPairBitmap = std::make_unique<wxBitmap>( image );
+        LayerPairBitmap = LAYER_PRESENTATION::CreateLayerPairIcon( background_color, top_color,
+                                                                   bottom_color, scale );
 
         if( m_auxiliaryToolBar )
         {