summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorxjm2017-10-30 15:46:59 -0500
committerxjm2017-10-30 15:46:59 -0500
commit1ebb2e1f32cadb5471859100d08645ea31211103 (patch)
tree9f71dae19e1e5ead375c1c09190d199f62a2075c
parent334beedad6a078b96db84c5d5fe7e245a39fb3ee (diff)
Issue #2660124 by tim.plunkett, samuel.mortenson, phenaproxima, tstoeckler, yoroy, dead_arm, xjm, Manuel Garcia, EclipseGc, larowlan, DyanneNova, tedbow: Dynamically build layout icons based on well formed config
-rw-r--r--core/core.libraries.yml1
-rw-r--r--core/lib/Drupal/Core/Layout/Annotation/Layout.php9
-rw-r--r--core/lib/Drupal/Core/Layout/Icon/IconBuilderInterface.php99
-rw-r--r--core/lib/Drupal/Core/Layout/Icon/SvgIconBuilder.php290
-rw-r--r--core/lib/Drupal/Core/Layout/LayoutDefinition.php88
-rw-r--r--core/misc/dialog/off-canvas.layout.css11
-rw-r--r--core/modules/field_layout/src/Form/FieldLayoutEntityDisplayFormTrait.php2
-rw-r--r--core/modules/layout_discovery/layout_discovery.layouts.yml20
-rw-r--r--core/modules/layout_discovery/layout_discovery.services.yml3
-rw-r--r--core/tests/Drupal/KernelTests/Core/Layout/IconBuilderTest.php137
-rw-r--r--core/themes/seven/css/layout/layout.css8
-rw-r--r--core/themes/stable/css/core/dialog/off-canvas.layout.css11
-rw-r--r--core/themes/stable/stable.info.yml1
13 files changed, 680 insertions, 0 deletions
diff --git a/core/core.libraries.yml b/core/core.libraries.yml
index 1ba7206..1b7d14d 100644
--- a/core/core.libraries.yml
+++ b/core/core.libraries.yml
@@ -929,6 +929,7 @@ drupal.dialog.off_canvas:
misc/dialog/off-canvas.details.css: {}
misc/dialog/off-canvas.tabledrag.css: {}
misc/dialog/off-canvas.dropbutton.css: {}
+ misc/dialog/off-canvas.layout.css: {}
dependencies:
- core/jquery
- core/drupal
diff --git a/core/lib/Drupal/Core/Layout/Annotation/Layout.php b/core/lib/Drupal/Core/Layout/Annotation/Layout.php
index d2072c2..eae930c 100644
--- a/core/lib/Drupal/Core/Layout/Annotation/Layout.php
+++ b/core/lib/Drupal/Core/Layout/Annotation/Layout.php
@@ -112,6 +112,15 @@ class Layout extends Plugin {
public $icon;
/**
+ * The icon map.
+ *
+ * @var string[][] optional
+ *
+ * @see \Drupal\Core\Layout\Icon\IconBuilderInterface::build()
+ */
+ public $icon_map;
+
+ /**
* An associative array of regions in this layout.
*
* The key of the array is the machine name of the region, and the value is
diff --git a/core/lib/Drupal/Core/Layout/Icon/IconBuilderInterface.php b/core/lib/Drupal/Core/Layout/Icon/IconBuilderInterface.php
new file mode 100644
index 0000000..de2f82a
--- /dev/null
+++ b/core/lib/Drupal/Core/Layout/Icon/IconBuilderInterface.php
@@ -0,0 +1,99 @@
+<?php
+
+namespace Drupal\Core\Layout\Icon;
+
+/**
+ * Provides an interface for building layout icons.
+ */
+interface IconBuilderInterface {
+
+ /**
+ * Builds a render array representation of an SVG based on an icon map.
+ *
+ * @param string[][] $icon_map
+ * A two-dimensional array representing the visual output of the layout.
+ * For the following shape:
+ * |------------------------------|
+ * | |
+ * | 100% |
+ * | |
+ * |-------|--------------|-------|
+ * | | | |
+ * | | 50% | 25% |
+ * | | | |
+ * | 25% |--------------|-------|
+ * | | |
+ * | | 75% |
+ * | | |
+ * |------------------------------|
+ * The corresponding array would be:
+ * - ['top']
+ * - ['first', 'second', 'second', 'third']
+ * - ['first', 'bottom', 'bottom', 'bottom'].
+ *
+ * @return array
+ * A render array representing a SVG icon.
+ */
+ public function build(array $icon_map);
+
+ /**
+ * Sets the ID.
+ *
+ * @param string $id
+ * The machine name of the layout.
+ *
+ * @return $this
+ */
+ public function setId($id);
+
+ /**
+ * Sets the label.
+ *
+ * @param string $label
+ * The label of the layout.
+ *
+ * @return $this
+ */
+ public function setLabel($label);
+
+ /**
+ * Sets the width.
+ *
+ * @param int $width
+ * The width of the SVG.
+ *
+ * @return $this
+ */
+ public function setWidth($width);
+
+ /**
+ * Sets the height.
+ *
+ * @param int $height
+ * The height of the SVG.
+ *
+ * @return $this
+ */
+ public function setHeight($height);
+
+ /**
+ * Sets the padding.
+ *
+ * @param int $padding
+ * The padding between regions.
+ *
+ * @return $this
+ */
+ public function setPadding($padding);
+
+ /**
+ * Sets the stroke width.
+ *
+ * @param int|null $stroke_width
+ * The width of region borders.
+ *
+ * @return $this
+ */
+ public function setStrokeWidth($stroke_width);
+
+}
diff --git a/core/lib/Drupal/Core/Layout/Icon/SvgIconBuilder.php b/core/lib/Drupal/Core/Layout/Icon/SvgIconBuilder.php
new file mode 100644
index 0000000..e7218b3
--- /dev/null
+++ b/core/lib/Drupal/Core/Layout/Icon/SvgIconBuilder.php
@@ -0,0 +1,290 @@
+<?php
+
+namespace Drupal\Core\Layout\Icon;
+
+use Drupal\Component\Utility\Html;
+
+/**
+ * Builds SVG layout icons.
+ */
+class SvgIconBuilder implements IconBuilderInterface {
+
+ /**
+ * The machine name of the layout.
+ *
+ * @var string
+ */
+ protected $id;
+
+ /**
+ * The label of the layout.
+ *
+ * @var string
+ */
+ protected $label;
+
+ /**
+ * The width of the SVG.
+ *
+ * @var int
+ */
+ protected $width = 125;
+
+ /**
+ * The height of the SVG.
+ *
+ * @var int
+ */
+ protected $height = 150;
+
+ /**
+ * The padding between regions.
+ *
+ * @var int
+ */
+ protected $padding = 4;
+
+ /**
+ * The width of region borders.
+ *
+ * @var int|null
+ */
+ protected $strokeWidth = 1;
+
+ /**
+ * {@inheritdoc}
+ */
+ public function build(array $icon_map) {
+ $regions = $this->calculateSvgValues($icon_map, $this->width, $this->height, $this->strokeWidth, $this->padding);
+ return $this->buildRenderArray($regions, $this->width, $this->height, $this->strokeWidth);
+ }
+
+ /**
+ * Builds a render array representation of an SVG.
+ *
+ * @param mixed[] $regions
+ * An array keyed by region name, with each element containing the 'height',
+ * 'width', and 'x' and 'y' offsets of each region.
+ * @param int $width
+ * The width of the SVG.
+ * @param int $height
+ * The height of the SVG.
+ * @param int|null $stroke_width
+ * The width of region borders.
+ *
+ * @return array
+ * A render array representing a SVG icon.
+ */
+ protected function buildRenderArray(array $regions, $width, $height, $stroke_width) {
+ $build = [
+ '#type' => 'html_tag',
+ '#tag' => 'svg',
+ '#attributes' => [
+ 'width' => $width,
+ 'height' => $height,
+ 'class' => [
+ 'layout-icon',
+ ],
+ ],
+ ];
+
+ if ($this->id) {
+ $build['#attributes']['class'][] = Html::getClass('layout-icon--' . $this->id);
+ }
+
+ if ($this->label) {
+ $build['title'] = [
+ '#type' => 'html_tag',
+ '#tag' => 'title',
+ '#value' => $this->label,
+ ];
+ }
+
+ // Append each polygon to the SVG.
+ foreach ($regions as $region => $attributes) {
+ // Wrapping with a <g> element allows for metadata to exist alongside the
+ // rectangle.
+ $build['region'][$region] = [
+ '#type' => 'html_tag',
+ '#tag' => 'g',
+ ];
+
+ $build['region'][$region]['title'] = [
+ '#type' => 'html_tag',
+ '#tag' => 'title',
+ '#value' => $region,
+ ];
+
+ // Assemble the rectangle SVG element.
+ $build['region'][$region]['rect'] = [
+ '#type' => 'html_tag',
+ '#tag' => 'rect',
+ '#attributes' => [
+ 'x' => $attributes['x'],
+ 'y' => $attributes['y'],
+ 'width' => $attributes['width'],
+ 'height' => $attributes['height'],
+ 'stroke-width' => $stroke_width,
+ 'class' => [
+ 'layout-icon__region',
+ Html::getClass('layout-icon__region--' . $region),
+ ],
+ ],
+ ];
+ }
+
+ return $build;
+ }
+
+ /**
+ * Calculates the dimensions and offsets of all regions.
+ *
+ * @param string[][] $rows
+ * A two-dimensional array representing the visual output of the layout. See
+ * the documentation for the $icon_map parameter of
+ * \Drupal\Core\Layout\Icon\IconBuilderInterface::build().
+ * @param int $width
+ * The width of the SVG.
+ * @param int $height
+ * The height of the SVG.
+ * @param int $stroke_width
+ * The width of region borders.
+ * @param int $padding
+ * The padding between regions.
+ *
+ * @return mixed[][]
+ * An array keyed by region name, with each element containing the 'height',
+ * 'width', and 'x' and 'y' offsets of each region.
+ */
+ protected function calculateSvgValues(array $rows, $width, $height, $stroke_width, $padding) {
+ $region_rects = [];
+
+ $row_height = $this->getLength(count($rows), $height, $stroke_width, $padding);
+ foreach ($rows as $row => $cols) {
+ $column_width = $this->getLength(count($cols), $width, $stroke_width, $padding);
+ $vertical_offset = $this->getOffset($row, $row_height, $stroke_width, $padding);
+ foreach ($cols as $col => $region) {
+ $horizontal_offset = $this->getOffset($col, $column_width, $stroke_width, $padding);
+
+ // Check if this region is new, or already exists in the rectangle.
+ if (!isset($region_rects[$region])) {
+ $region_rects[$region] = [
+ 'x' => $horizontal_offset,
+ 'y' => $vertical_offset,
+ 'width' => $column_width,
+ 'height' => $row_height,
+ ];
+ }
+ else {
+ // In order to include the area of the previous region and any padding
+ // or border, subtract the calculated offset from the original offset.
+ $region_rects[$region]['width'] = $column_width + ($horizontal_offset - $region_rects[$region]['x']);
+ $region_rects[$region]['height'] = $row_height + ($vertical_offset - $region_rects[$region]['y']);
+ }
+ }
+ }
+
+ return $region_rects;
+ }
+
+ /**
+ * Gets the offset for this region.
+ *
+ * @param int $delta
+ * The zero-based delta of the region.
+ * @param int $length
+ * The height or width of the region.
+ * @param int $stroke_width
+ * The width of the region borders.
+ * @param int $padding
+ * The padding between regions.
+ *
+ * @return int
+ * The offset for this region.
+ */
+ protected function getOffset($delta, $length, $stroke_width, $padding) {
+ // Half of the stroke width is drawn outside the dimensions.
+ $stroke_width /= 2;
+ // For every region in front of this add two strokes, as well as one
+ // directly in front.
+ $num_of_strokes = 2 * $delta + 1;
+ return ($num_of_strokes * $stroke_width) + ($delta * ($length + $padding));
+ }
+
+ /**
+ * Gets the height or width of a region.
+ *
+ * @param int $number_of_regions
+ * The total number of regions.
+ * @param int $length
+ * The total height or width of the icon.
+ * @param int $stroke_width
+ * The width of the region borders.
+ * @param int $padding
+ * The padding between regions.
+ *
+ * @return float|int
+ * The height or width of a region.
+ */
+ protected function getLength($number_of_regions, $length, $stroke_width, $padding) {
+ if ($number_of_regions === 0) {
+ return 0;
+ }
+
+ // Half of the stroke width is drawn outside the dimensions.
+ $total_stroke = $number_of_regions * $stroke_width;
+ // Padding does not precede the first region.
+ $total_padding = ($number_of_regions - 1) * $padding;
+ // Divide the remaining length by the number of regions.
+ return ($length - $total_padding - $total_stroke) / $number_of_regions;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function setId($id) {
+ $this->id = $id;
+ return $this;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function setLabel($label) {
+ $this->label = $label;
+ return $this;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function setWidth($width) {
+ $this->width = $width;
+ return $this;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function setHeight($height) {
+ $this->height = $height;
+ return $this;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function setPadding($padding) {
+ $this->padding = $padding;
+ return $this;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function setStrokeWidth($stroke_width) {
+ $this->strokeWidth = $stroke_width;
+ return $this;
+ }
+
+}
diff --git a/core/lib/Drupal/Core/Layout/LayoutDefinition.php b/core/lib/Drupal/Core/Layout/LayoutDefinition.php
index c804776..c87b618 100644
--- a/core/lib/Drupal/Core/Layout/LayoutDefinition.php
+++ b/core/lib/Drupal/Core/Layout/LayoutDefinition.php
@@ -86,6 +86,15 @@ class LayoutDefinition extends PluginDefinition implements PluginDefinitionInter
protected $icon;
/**
+ * An array defining the regions of a layout.
+ *
+ * @var string[][]|null
+ *
+ * @see \Drupal\Core\Layout\Icon\IconBuilderInterface::build()
+ */
+ protected $icon_map;
+
+ /**
* An associative array of regions in this layout.
*
* The key of the array is the machine name of the region, and the value is
@@ -372,6 +381,85 @@ class LayoutDefinition extends PluginDefinition implements PluginDefinitionInter
}
/**
+ * Gets the icon map for this layout definition.
+ *
+ * This should not be used if an icon path is specified. See ::getIcon().
+ *
+ * @return string[][]|null
+ * The icon map, if it exists.
+ */
+ public function getIconMap() {
+ return $this->icon_map;
+ }
+
+ /**
+ * Sets the icon map for this layout definition.
+ *
+ * @param string[][]|null $icon_map
+ * The icon map.
+ *
+ * @return $this
+ */
+ public function setIconMap($icon_map) {
+ $this->icon_map = $icon_map;
+ return $this;
+ }
+
+ /**
+ * Builds a render array for an icon representing the layout.
+ *
+ * @param int $width
+ * (optional) The width of the icon. Defaults to 125.
+ * @param int $height
+ * (optional) The height of the icon. Defaults to 150.
+ * @param int $stroke_width
+ * (optional) If an icon map is used, the width of region borders.
+ * @param int $padding
+ * (optional) If an icon map is used, the padding between regions. Any
+ * value above 0 is valid.
+ *
+ * @return array
+ * A render array for the icon.
+ */
+ public function getIcon($width = 125, $height = 150, $stroke_width = NULL, $padding = NULL) {
+ $icon = [];
+ if ($icon_path = $this->getIconPath()) {
+ $icon = [
+ '#theme' => 'image',
+ '#uri' => $icon_path,
+ '#width' => $width,
+ '#height' => $height,
+ '#alt' => $this->getLabel(),
+ ];
+ }
+ elseif ($icon_map = $this->getIconMap()) {
+ $icon_builder = $this->getIconBuilder()
+ ->setId($this->id())
+ ->setLabel($this->getLabel())
+ ->setWidth($width)
+ ->setHeight($height);
+ if ($padding) {
+ $icon_builder->setPadding($padding);
+ }
+ if ($stroke_width) {
+ $icon_builder->setStrokeWidth($stroke_width);
+ }
+ $icon = $icon_builder->build($icon_map);
+ }
+ return $icon;
+ }
+
+ /**
+ * Wraps the icon builder.
+ *
+ * @return \Drupal\Core\Layout\Icon\IconBuilderInterface
+ * The icon builder.
+ */
+ protected function getIconBuilder() {
+ return \Drupal::service('layout.icon_builder');
+ }
+
+ /**
* Gets the regions for this layout definition.
*
* @return array[]
diff --git a/core/misc/dialog/off-canvas.layout.css b/core/misc/dialog/off-canvas.layout.css
new file mode 100644
index 0000000..aa3a537
--- /dev/null
+++ b/core/misc/dialog/off-canvas.layout.css
@@ -0,0 +1,11 @@
+/**
+ * @file
+ * Visual styling for layouts in the off-canvas dialog.
+ *
+ * See seven/css/layout/layout.css
+ */
+
+.layout-icon__region {
+ fill: #f5f5f2;
+ stroke: #666;
+}
diff --git a/core/modules/field_layout/src/Form/FieldLayoutEntityDisplayFormTrait.php b/core/modules/field_layout/src/Form/FieldLayoutEntityDisplayFormTrait.php
index 043e5c7..170bcd0 100644
--- a/core/modules/field_layout/src/Form/FieldLayoutEntityDisplayFormTrait.php
+++ b/core/modules/field_layout/src/Form/FieldLayoutEntityDisplayFormTrait.php
@@ -87,6 +87,8 @@ trait FieldLayoutEntityDisplayFormTrait {
'#tree' => TRUE,
];
+ $form['field_layouts']['settings_wrapper']['icon'] = $layout_plugin->getPluginDefinition()->getIcon();
+
if ($layout_plugin instanceof PluginFormInterface) {
$form['field_layouts']['settings_wrapper']['layout_settings'] = [];
$subform_state = SubformState::createForSubform($form['field_layouts']['settings_wrapper']['layout_settings'], $form, $form_state);
diff --git a/core/modules/layout_discovery/layout_discovery.layouts.yml b/core/modules/layout_discovery/layout_discovery.layouts.yml
index d1b0e5a..755a96b 100644
--- a/core/modules/layout_discovery/layout_discovery.layouts.yml
+++ b/core/modules/layout_discovery/layout_discovery.layouts.yml
@@ -5,6 +5,8 @@ layout_onecol:
library: layout_discovery/onecol
category: 'Columns: 1'
default_region: content
+ icon_map:
+ - [content]
regions:
content:
label: Content
@@ -16,6 +18,10 @@ layout_twocol:
library: layout_discovery/twocol
category: 'Columns: 2'
default_region: first
+ icon_map:
+ - [top]
+ - [first, second]
+ - [bottom]
regions:
top:
label: Top
@@ -33,6 +39,12 @@ layout_twocol_bricks:
library: layout_discovery/twocol_bricks
category: 'Columns: 2'
default_region: middle
+ icon_map:
+ - [top]
+ - [first_above, second_above]
+ - [middle]
+ - [first_below, second_below]
+ - [bottom]
regions:
top:
label: Top
@@ -56,6 +68,10 @@ layout_threecol_25_50_25:
library: layout_discovery/threecol_25_50_25
category: 'Columns: 3'
default_region: second
+ icon_map:
+ - [top]
+ - [first, second, second, third]
+ - [bottom]
regions:
top:
label: Top
@@ -75,6 +91,10 @@ layout_threecol_33_34_33:
library: layout_discovery/threecol_33_34_33
category: 'Columns: 3'
default_region: first
+ icon_map:
+ - [top]
+ - [first, second, third]
+ - [bottom]
regions:
top:
label: Top
diff --git a/core/modules/layout_discovery/layout_discovery.services.yml b/core/modules/layout_discovery/layout_discovery.services.yml
index 1e24db4..48d3229 100644
--- a/core/modules/layout_discovery/layout_discovery.services.yml
+++ b/core/modules/layout_discovery/layout_discovery.services.yml
@@ -2,3 +2,6 @@ services:
plugin.manager.core.layout:
class: Drupal\Core\Layout\LayoutPluginManager
arguments: ['@container.namespaces', '@cache.discovery', '@module_handler', '@theme_handler']
+ layout.icon_builder:
+ class: Drupal\Core\Layout\Icon\SvgIconBuilder
+ shared: false
diff --git a/core/tests/Drupal/KernelTests/Core/Layout/IconBuilderTest.php b/core/tests/Drupal/KernelTests/Core/Layout/IconBuilderTest.php
new file mode 100644
index 0000000..92ac3ce
--- /dev/null
+++ b/core/tests/Drupal/KernelTests/Core/Layout/IconBuilderTest.php
@@ -0,0 +1,137 @@
+<?php
+
+namespace Drupal\KernelTests\Core\Layout;
+
+use Drupal\Core\Layout\Icon\SvgIconBuilder;
+use Drupal\Core\Render\RenderContext;
+use Drupal\KernelTests\KernelTestBase;
+
+/**
+ * @coversDefaultClass \Drupal\Core\Layout\Icon\SvgIconBuilder
+ * @group Layout
+ */
+class IconBuilderTest extends KernelTestBase {
+
+ /**
+ * @covers ::build
+ * @covers ::buildRenderArray
+ * @covers ::calculateSvgValues
+ * @covers ::getLength
+ * @covers ::getOffset
+ *
+ * @dataProvider providerTestBuild
+ */
+ public function testBuild(SvgIconBuilder $icon_builder, $icon_map, $expected) {
+ $renderer = $this->container->get('renderer');
+
+ $build = $icon_builder->build($icon_map);
+
+ $output = (string) $renderer->executeInRenderContext(new RenderContext(), function () use ($build, $renderer) {
+ return $renderer->render($build);
+ });
+ $this->assertSame($expected, $output);
+ }
+
+ public function providerTestBuild() {
+ $data = [];
+ $data['empty'][] = (new SvgIconBuilder());
+ $data['empty'][] = [];
+ $data['empty'][] = <<<'EOD'
+<svg width="125" height="150" class="layout-icon"></svg>
+
+EOD;
+
+ $data['two_column'][] = (new SvgIconBuilder())
+ ->setId('two_column')
+ ->setLabel('Two Column')
+ ->setWidth(250)
+ ->setHeight(300)
+ ->setStrokeWidth(2);
+ $data['two_column'][] = [['left', 'right']];
+ $data['two_column'][] = <<<'EOD'
+<svg width="250" height="300" class="layout-icon layout-icon--two-column"><title>Two Column</title>
+<g><title>left</title>
+<rect x="1" y="1" width="121" height="298" stroke-width="2" class="layout-icon__region layout-icon__region--left" />
+</g>
+<g><title>right</title>
+<rect x="128" y="1" width="121" height="298" stroke-width="2" class="layout-icon__region layout-icon__region--right" />
+</g>
+</svg>
+
+EOD;
+
+ $data['two_column_no_stroke'][] = (new SvgIconBuilder())
+ ->setWidth(250)
+ ->setHeight(300)
+ ->setStrokeWidth(NULL);
+ $data['two_column_no_stroke'][] = [['left', 'right']];
+ $data['two_column_no_stroke'][] = <<<'EOD'
+<svg width="250" height="300" class="layout-icon"><g><title>left</title>
+<rect x="0" y="0" width="123" height="300" class="layout-icon__region layout-icon__region--left" />
+</g>
+<g><title>right</title>
+<rect x="127" y="0" width="123" height="300" class="layout-icon__region layout-icon__region--right" />
+</g>
+</svg>
+
+EOD;
+
+ $data['two_column_border_collapse'][] = (new SvgIconBuilder())
+ ->setWidth(250)
+ ->setHeight(300)
+ ->setStrokeWidth(2)
+ ->setPadding(-2);
+ $data['two_column_border_collapse'][] = [['left', 'right']];
+ $data['two_column_border_collapse'][] = <<<'EOD'
+<svg width="250" height="300" class="layout-icon"><g><title>left</title>
+<rect x="1" y="1" width="124" height="298" stroke-width="2" class="layout-icon__region layout-icon__region--left" />
+</g>
+<g><title>right</title>
+<rect x="125" y="1" width="124" height="298" stroke-width="2" class="layout-icon__region layout-icon__region--right" />
+</g>
+</svg>
+
+EOD;
+
+ $data['stacked'][] = (new SvgIconBuilder())
+ ->setStrokeWidth(2);
+ $data['stacked'][] = [
+ ['sidebar', 'top', 'top'],
+ ['sidebar', 'left', 'right'],
+ ['sidebar', 'middle', 'middle'],
+ ['footer_left', 'footer_right'],
+ ['footer_full'],
+ ];
+ $data['stacked'][] = <<<'EOD'
+<svg width="125" height="150" class="layout-icon"><g><title>sidebar</title>
+<rect x="1" y="1" width="37" height="86.4" stroke-width="2" class="layout-icon__region layout-icon__region--sidebar" />
+</g>
+<g><title>top</title>
+<rect x="44" y="1" width="80" height="24.8" stroke-width="2" class="layout-icon__region layout-icon__region--top" />
+</g>
+<g><title>left</title>
+<rect x="44" y="31.8" width="37" height="24.8" stroke-width="2" class="layout-icon__region layout-icon__region--left" />
+</g>
+<g><title>right</title>
+<rect x="87" y="31.8" width="37" height="24.8" stroke-width="2" class="layout-icon__region layout-icon__region--right" />
+</g>
+<g><title>middle</title>
+<rect x="44" y="62.6" width="80" height="24.8" stroke-width="2" class="layout-icon__region layout-icon__region--middle" />
+</g>
+<g><title>footer_left</title>
+<rect x="1" y="93.4" width="58.5" height="24.8" stroke-width="2" class="layout-icon__region layout-icon__region--footer-left" />
+</g>
+<g><title>footer_right</title>
+<rect x="65.5" y="93.4" width="58.5" height="24.8" stroke-width="2" class="layout-icon__region layout-icon__region--footer-right" />
+</g>
+<g><title>footer_full</title>
+<rect x="1" y="124.2" width="123" height="24.8" stroke-width="2" class="layout-icon__region layout-icon__region--footer-full" />
+</g>
+</svg>
+
+EOD;
+
+ return $data;
+ }
+
+}
diff --git a/core/themes/seven/css/layout/layout.css b/core/themes/seven/css/layout/layout.css
index eb7c2bf..39649e4 100644
--- a/core/themes/seven/css/layout/layout.css
+++ b/core/themes/seven/css/layout/layout.css
@@ -4,3 +4,11 @@
.page-content {
margin-bottom: 80px;
}
+
+/**
+ * Add color to layout icons.
+ */
+.layout-icon__region {
+ fill: #f5f5f2;
+ stroke: #666;
+}
diff --git a/core/themes/stable/css/core/dialog/off-canvas.layout.css b/core/themes/stable/css/core/dialog/off-canvas.layout.css
new file mode 100644
index 0000000..aa3a537
--- /dev/null
+++ b/core/themes/stable/css/core/dialog/off-canvas.layout.css
@@ -0,0 +1,11 @@
+/**
+ * @file
+ * Visual styling for layouts in the off-canvas dialog.
+ *
+ * See seven/css/layout/layout.css
+ */
+
+.layout-icon__region {
+ fill: #f5f5f2;
+ stroke: #666;
+}
diff --git a/core/themes/stable/stable.info.yml b/core/themes/stable/stable.info.yml
index 1e437f9..957a65c 100644
--- a/core/themes/stable/stable.info.yml
+++ b/core/themes/stable/stable.info.yml
@@ -72,6 +72,7 @@ libraries-override:
misc/dialog/off-canvas.details.css: css/core/dialog/off-canvas.details.css
misc/dialog/off-canvas.tabledrag.css: css/core/dialog/off-canvas.tabledrag.css
misc/dialog/off-canvas.dropbutton.css: css/core/dialog/off-canvas.dropbutton.css
+ misc/dialog/off-canvas.layout.css: css/core/dialog/off-canvas.layout.css
core/drupal.dropbutton:
css: