diff --git a/examples/Demo/Builds/Android/app/CMakeLists.txt b/examples/Demo/Builds/Android/app/CMakeLists.txt index 013b3f0fe5..d7d0abf025 100644 --- a/examples/Demo/Builds/Android/app/CMakeLists.txt +++ b/examples/Demo/Builds/Android/app/CMakeLists.txt @@ -51,6 +51,7 @@ add_library( ${BINARY_NAME} "../../../Source/Demos/FlexBoxDemo.cpp" "../../../Source/Demos/FontsDemo.cpp" "../../../Source/Demos/GraphicsDemo.cpp" + "../../../Source/Demos/GridDemo.cpp" "../../../Source/Demos/ImagesDemo.cpp" "../../../Source/Demos/JavaScript.cpp" "../../../Source/Demos/KeyMappingsDemo.cpp" @@ -1099,6 +1100,11 @@ add_library( ${BINARY_NAME} "../../../../../modules/juce_gui_basics/layout/juce_FlexBox.cpp" "../../../../../modules/juce_gui_basics/layout/juce_FlexBox.h" "../../../../../modules/juce_gui_basics/layout/juce_FlexItem.h" + "../../../../../modules/juce_gui_basics/layout/juce_Grid.cpp" + "../../../../../modules/juce_gui_basics/layout/juce_Grid.h" + "../../../../../modules/juce_gui_basics/layout/juce_GridItem.cpp" + "../../../../../modules/juce_gui_basics/layout/juce_GridItem.h" + "../../../../../modules/juce_gui_basics/layout/juce_GridUnitTests.cpp" "../../../../../modules/juce_gui_basics/layout/juce_GroupComponent.cpp" "../../../../../modules/juce_gui_basics/layout/juce_GroupComponent.h" "../../../../../modules/juce_gui_basics/layout/juce_MultiDocumentPanel.cpp" @@ -2410,6 +2416,11 @@ set_source_files_properties("../../../../../modules/juce_gui_basics/layout/juce_ set_source_files_properties("../../../../../modules/juce_gui_basics/layout/juce_FlexBox.cpp" PROPERTIES HEADER_FILE_ONLY TRUE) set_source_files_properties("../../../../../modules/juce_gui_basics/layout/juce_FlexBox.h" PROPERTIES HEADER_FILE_ONLY TRUE) set_source_files_properties("../../../../../modules/juce_gui_basics/layout/juce_FlexItem.h" PROPERTIES HEADER_FILE_ONLY TRUE) +set_source_files_properties("../../../../../modules/juce_gui_basics/layout/juce_Grid.cpp" PROPERTIES HEADER_FILE_ONLY TRUE) +set_source_files_properties("../../../../../modules/juce_gui_basics/layout/juce_Grid.h" PROPERTIES HEADER_FILE_ONLY TRUE) +set_source_files_properties("../../../../../modules/juce_gui_basics/layout/juce_GridItem.cpp" PROPERTIES HEADER_FILE_ONLY TRUE) +set_source_files_properties("../../../../../modules/juce_gui_basics/layout/juce_GridItem.h" PROPERTIES HEADER_FILE_ONLY TRUE) +set_source_files_properties("../../../../../modules/juce_gui_basics/layout/juce_GridUnitTests.cpp" PROPERTIES HEADER_FILE_ONLY TRUE) set_source_files_properties("../../../../../modules/juce_gui_basics/layout/juce_GroupComponent.cpp" PROPERTIES HEADER_FILE_ONLY TRUE) set_source_files_properties("../../../../../modules/juce_gui_basics/layout/juce_GroupComponent.h" PROPERTIES HEADER_FILE_ONLY TRUE) set_source_files_properties("../../../../../modules/juce_gui_basics/layout/juce_MultiDocumentPanel.cpp" PROPERTIES HEADER_FILE_ONLY TRUE) diff --git a/examples/Demo/Builds/LinuxMakefile/Makefile b/examples/Demo/Builds/LinuxMakefile/Makefile index 10241f2bb9..7f2a782475 100644 --- a/examples/Demo/Builds/LinuxMakefile/Makefile +++ b/examples/Demo/Builds/LinuxMakefile/Makefile @@ -85,6 +85,7 @@ OBJECTS_APP := \ $(JUCE_OBJDIR)/FlexBoxDemo_e4f3d9b7.o \ $(JUCE_OBJDIR)/FontsDemo_6da1e5e9.o \ $(JUCE_OBJDIR)/GraphicsDemo_85142ef6.o \ + $(JUCE_OBJDIR)/GridDemo_63e53631.o \ $(JUCE_OBJDIR)/ImagesDemo_ecf13aa3.o \ $(JUCE_OBJDIR)/JavaScript_d6e42eb5.o \ $(JUCE_OBJDIR)/KeyMappingsDemo_cf05c709.o \ @@ -230,6 +231,11 @@ $(JUCE_OBJDIR)/GraphicsDemo_85142ef6.o: ../../Source/Demos/GraphicsDemo.cpp @echo "Compiling GraphicsDemo.cpp" $(V_AT)$(CXX) $(JUCE_CXXFLAGS) $(JUCE_CPPFLAGS_APP) $(JUCE_CFLAGS_APP) -o "$@" -c "$<" +$(JUCE_OBJDIR)/GridDemo_63e53631.o: ../../Source/Demos/GridDemo.cpp + -$(V_AT)mkdir -p $(JUCE_OBJDIR) + @echo "Compiling GridDemo.cpp" + $(V_AT)$(CXX) $(JUCE_CXXFLAGS) $(JUCE_CPPFLAGS_APP) $(JUCE_CFLAGS_APP) -o "$@" -c "$<" + $(JUCE_OBJDIR)/ImagesDemo_ecf13aa3.o: ../../Source/Demos/ImagesDemo.cpp -$(V_AT)mkdir -p $(JUCE_OBJDIR) @echo "Compiling ImagesDemo.cpp" diff --git a/examples/Demo/Builds/MacOSX/JuceDemo.xcodeproj/project.pbxproj b/examples/Demo/Builds/MacOSX/JuceDemo.xcodeproj/project.pbxproj index c1aac64631..75bca5e570 100644 --- a/examples/Demo/Builds/MacOSX/JuceDemo.xcodeproj/project.pbxproj +++ b/examples/Demo/Builds/MacOSX/JuceDemo.xcodeproj/project.pbxproj @@ -42,6 +42,7 @@ 2BFF2E7E13527E2E764617AA = {isa = PBXBuildFile; fileRef = 7AEEF2EE40D3B8AE687C6618; }; E4A67DD76D4D063F547F0844 = {isa = PBXBuildFile; fileRef = 4DB00B418C4F068BC5FC7314; }; 257CBA785DEDAF53F4E9BF60 = {isa = PBXBuildFile; fileRef = 4D569839066D92C393F58EB4; }; + EC00D5BAB8AF26746D6AC956 = {isa = PBXBuildFile; fileRef = 3EBB84376D0639930D3ECA78; }; 9C30D9613D76EEFDB1653F34 = {isa = PBXBuildFile; fileRef = 7DBF83B26277D13CB3FC1479; }; B810E94ECBCA231F60EBEA5F = {isa = PBXBuildFile; fileRef = CCEC8F9385AE939B24D27954; }; 15B2A7314D1E347D65EC664D = {isa = PBXBuildFile; fileRef = F1E995A1E00C6545A4C3297B; }; @@ -105,6 +106,7 @@ 3375D74953976C15513FA6AE = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; name = "include_juce_events.mm"; path = "../../JuceLibraryCode/include_juce_events.mm"; sourceTree = "SOURCE_ROOT"; }; 3482A5FDDEE5E5D405047999 = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = WidgetsDemo.cpp; path = ../../Source/Demos/WidgetsDemo.cpp; sourceTree = "SOURCE_ROOT"; }; 39A06D3252DDA6F97FB09495 = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = OpenGLDemo2D.cpp; path = ../../Source/Demos/OpenGLDemo2D.cpp; sourceTree = "SOURCE_ROOT"; }; + 3EBB84376D0639930D3ECA78 = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = GridDemo.cpp; path = ../../Source/Demos/GridDemo.cpp; sourceTree = "SOURCE_ROOT"; }; 4621682C1A4E85A088F7CEE0 = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Carbon.framework; path = System/Library/Frameworks/Carbon.framework; sourceTree = SDKROOT; }; 49412C044B88516D9AF59FDD = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = CryptographyDemo.cpp; path = ../../Source/Demos/CryptographyDemo.cpp; sourceTree = "SOURCE_ROOT"; }; 4975DB849F14413317E94609 = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = AppConfig.h; path = ../../JuceLibraryCode/AppConfig.h; sourceTree = "SOURCE_ROOT"; }; @@ -205,6 +207,7 @@ 7AEEF2EE40D3B8AE687C6618, 4DB00B418C4F068BC5FC7314, 4D569839066D92C393F58EB4, + 3EBB84376D0639930D3ECA78, 7DBF83B26277D13CB3FC1479, CCEC8F9385AE939B24D27954, F1E995A1E00C6545A4C3297B, @@ -484,6 +487,7 @@ 2BFF2E7E13527E2E764617AA, E4A67DD76D4D063F547F0844, 257CBA785DEDAF53F4E9BF60, + EC00D5BAB8AF26746D6AC956, 9C30D9613D76EEFDB1653F34, B810E94ECBCA231F60EBEA5F, 15B2A7314D1E347D65EC664D, diff --git a/examples/Demo/Builds/VisualStudio2013/JuceDemo_App.vcxproj b/examples/Demo/Builds/VisualStudio2013/JuceDemo_App.vcxproj index 1feb228a4c..c88e96845f 100644 --- a/examples/Demo/Builds/VisualStudio2013/JuceDemo_App.vcxproj +++ b/examples/Demo/Builds/VisualStudio2013/JuceDemo_App.vcxproj @@ -167,6 +167,7 @@ + @@ -1578,6 +1579,15 @@ true + + true + + + true + + + true + true @@ -2463,6 +2473,8 @@ + + diff --git a/examples/Demo/Builds/VisualStudio2013/JuceDemo_App.vcxproj.filters b/examples/Demo/Builds/VisualStudio2013/JuceDemo_App.vcxproj.filters index 77633e7dc5..89b35da17b 100644 --- a/examples/Demo/Builds/VisualStudio2013/JuceDemo_App.vcxproj.filters +++ b/examples/Demo/Builds/VisualStudio2013/JuceDemo_App.vcxproj.filters @@ -466,6 +466,9 @@ JuceDemo\Source\Demos + + JuceDemo\Source\Demos + JuceDemo\Source\Demos @@ -1993,6 +1996,15 @@ Juce Modules\juce_gui_basics\layout + + Juce Modules\juce_gui_basics\layout + + + Juce Modules\juce_gui_basics\layout + + + Juce Modules\juce_gui_basics\layout + Juce Modules\juce_gui_basics\layout @@ -4011,6 +4023,12 @@ Juce Modules\juce_gui_basics\layout + + Juce Modules\juce_gui_basics\layout + + + Juce Modules\juce_gui_basics\layout + Juce Modules\juce_gui_basics\layout diff --git a/examples/Demo/Builds/VisualStudio2015/JuceDemo_App.vcxproj b/examples/Demo/Builds/VisualStudio2015/JuceDemo_App.vcxproj index 140ea55ff2..c2318f8c05 100644 --- a/examples/Demo/Builds/VisualStudio2015/JuceDemo_App.vcxproj +++ b/examples/Demo/Builds/VisualStudio2015/JuceDemo_App.vcxproj @@ -166,6 +166,7 @@ + @@ -1577,6 +1578,15 @@ true + + true + + + true + + + true + true @@ -2462,6 +2472,8 @@ + + diff --git a/examples/Demo/Builds/VisualStudio2015/JuceDemo_App.vcxproj.filters b/examples/Demo/Builds/VisualStudio2015/JuceDemo_App.vcxproj.filters index f199e23038..2c87722083 100644 --- a/examples/Demo/Builds/VisualStudio2015/JuceDemo_App.vcxproj.filters +++ b/examples/Demo/Builds/VisualStudio2015/JuceDemo_App.vcxproj.filters @@ -466,6 +466,9 @@ JuceDemo\Source\Demos + + JuceDemo\Source\Demos + JuceDemo\Source\Demos @@ -1993,6 +1996,15 @@ Juce Modules\juce_gui_basics\layout + + Juce Modules\juce_gui_basics\layout + + + Juce Modules\juce_gui_basics\layout + + + Juce Modules\juce_gui_basics\layout + Juce Modules\juce_gui_basics\layout @@ -4011,6 +4023,12 @@ Juce Modules\juce_gui_basics\layout + + Juce Modules\juce_gui_basics\layout + + + Juce Modules\juce_gui_basics\layout + Juce Modules\juce_gui_basics\layout diff --git a/examples/Demo/Builds/VisualStudio2017/JuceDemo_App.vcxproj b/examples/Demo/Builds/VisualStudio2017/JuceDemo_App.vcxproj index 231b42bdf1..4477633aab 100644 --- a/examples/Demo/Builds/VisualStudio2017/JuceDemo_App.vcxproj +++ b/examples/Demo/Builds/VisualStudio2017/JuceDemo_App.vcxproj @@ -166,6 +166,7 @@ + @@ -1577,6 +1578,15 @@ true + + true + + + true + + + true + true @@ -2462,6 +2472,8 @@ + + diff --git a/examples/Demo/Builds/VisualStudio2017/JuceDemo_App.vcxproj.filters b/examples/Demo/Builds/VisualStudio2017/JuceDemo_App.vcxproj.filters index b97f6d12df..6c11e67fa3 100644 --- a/examples/Demo/Builds/VisualStudio2017/JuceDemo_App.vcxproj.filters +++ b/examples/Demo/Builds/VisualStudio2017/JuceDemo_App.vcxproj.filters @@ -466,6 +466,9 @@ JuceDemo\Source\Demos + + JuceDemo\Source\Demos + JuceDemo\Source\Demos @@ -1993,6 +1996,15 @@ Juce Modules\juce_gui_basics\layout + + Juce Modules\juce_gui_basics\layout + + + Juce Modules\juce_gui_basics\layout + + + Juce Modules\juce_gui_basics\layout + Juce Modules\juce_gui_basics\layout @@ -4011,6 +4023,12 @@ Juce Modules\juce_gui_basics\layout + + Juce Modules\juce_gui_basics\layout + + + Juce Modules\juce_gui_basics\layout + Juce Modules\juce_gui_basics\layout diff --git a/examples/Demo/Builds/iOS/JuceDemo.xcodeproj/project.pbxproj b/examples/Demo/Builds/iOS/JuceDemo.xcodeproj/project.pbxproj index c45d9f2db3..4a89ecd1f1 100644 --- a/examples/Demo/Builds/iOS/JuceDemo.xcodeproj/project.pbxproj +++ b/examples/Demo/Builds/iOS/JuceDemo.xcodeproj/project.pbxproj @@ -43,6 +43,7 @@ 2BFF2E7E13527E2E764617AA = {isa = PBXBuildFile; fileRef = 7AEEF2EE40D3B8AE687C6618; }; E4A67DD76D4D063F547F0844 = {isa = PBXBuildFile; fileRef = 4DB00B418C4F068BC5FC7314; }; 257CBA785DEDAF53F4E9BF60 = {isa = PBXBuildFile; fileRef = 4D569839066D92C393F58EB4; }; + EC00D5BAB8AF26746D6AC956 = {isa = PBXBuildFile; fileRef = 3EBB84376D0639930D3ECA78; }; 9C30D9613D76EEFDB1653F34 = {isa = PBXBuildFile; fileRef = 7DBF83B26277D13CB3FC1479; }; B810E94ECBCA231F60EBEA5F = {isa = PBXBuildFile; fileRef = CCEC8F9385AE939B24D27954; }; 15B2A7314D1E347D65EC664D = {isa = PBXBuildFile; fileRef = F1E995A1E00C6545A4C3297B; }; @@ -106,6 +107,7 @@ 3482A5FDDEE5E5D405047999 = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = WidgetsDemo.cpp; path = ../../Source/Demos/WidgetsDemo.cpp; sourceTree = "SOURCE_ROOT"; }; 37CB4A819F38E7AF9C77A8CB = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = System/Library/Frameworks/CoreGraphics.framework; sourceTree = SDKROOT; }; 39A06D3252DDA6F97FB09495 = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = OpenGLDemo2D.cpp; path = ../../Source/Demos/OpenGLDemo2D.cpp; sourceTree = "SOURCE_ROOT"; }; + 3EBB84376D0639930D3ECA78 = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = GridDemo.cpp; path = ../../Source/Demos/GridDemo.cpp; sourceTree = "SOURCE_ROOT"; }; 49412C044B88516D9AF59FDD = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = CryptographyDemo.cpp; path = ../../Source/Demos/CryptographyDemo.cpp; sourceTree = "SOURCE_ROOT"; }; 4975DB849F14413317E94609 = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = AppConfig.h; path = ../../JuceLibraryCode/AppConfig.h; sourceTree = "SOURCE_ROOT"; }; 4C1FFE22514EB2A178750B86 = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreImage.framework; path = System/Library/Frameworks/CoreImage.framework; sourceTree = SDKROOT; }; @@ -207,6 +209,7 @@ 7AEEF2EE40D3B8AE687C6618, 4DB00B418C4F068BC5FC7314, 4D569839066D92C393F58EB4, + 3EBB84376D0639930D3ECA78, 7DBF83B26277D13CB3FC1479, CCEC8F9385AE939B24D27954, F1E995A1E00C6545A4C3297B, @@ -493,6 +496,7 @@ 2BFF2E7E13527E2E764617AA, E4A67DD76D4D063F547F0844, 257CBA785DEDAF53F4E9BF60, + EC00D5BAB8AF26746D6AC956, 9C30D9613D76EEFDB1653F34, B810E94ECBCA231F60EBEA5F, 15B2A7314D1E347D65EC664D, diff --git a/examples/Demo/JuceDemo.jucer b/examples/Demo/JuceDemo.jucer index 185e055718..378cf064e0 100644 --- a/examples/Demo/JuceDemo.jucer +++ b/examples/Demo/JuceDemo.jucer @@ -235,6 +235,7 @@ + items; +}; + +// This static object will register this demo type in a global list of demos.. +static JuceDemoType demo ("10 Components: GridDemo"); diff --git a/modules/juce_gui_basics/juce_gui_basics.cpp b/modules/juce_gui_basics/juce_gui_basics.cpp index b710d391a2..16b3341fd6 100644 --- a/modules/juce_gui_basics/juce_gui_basics.cpp +++ b/modules/juce_gui_basics/juce_gui_basics.cpp @@ -128,6 +128,9 @@ #undef KeyPress #endif +#include +#include + //============================================================================== namespace juce { @@ -256,6 +259,9 @@ extern bool juce_areThereAnyAlwaysOnTopWindows(); // these classes are C++11-only #if JUCE_COMPILER_SUPPORTS_INITIALIZER_LISTS #include "layout/juce_FlexBox.cpp" + #include "layout/juce_GridItem.cpp" + #include "layout/juce_Grid.cpp" + #include "layout/juce_GridUnitTests.cpp" #endif #if JUCE_IOS || JUCE_WINDOWS diff --git a/modules/juce_gui_basics/juce_gui_basics.h b/modules/juce_gui_basics/juce_gui_basics.h index 6b98ff4a37..d4702aaa37 100644 --- a/modules/juce_gui_basics/juce_gui_basics.h +++ b/modules/juce_gui_basics/juce_gui_basics.h @@ -157,6 +157,7 @@ class KeyPressMappingSet; class ApplicationCommandManagerListener; class DrawableButton; class FlexBox; +class Grid; #include "mouse/juce_MouseCursor.h" #include "mouse/juce_MouseListener.h" @@ -295,6 +296,14 @@ class FlexBox; #if JUCE_COMPILER_SUPPORTS_INITIALIZER_LISTS #include "layout/juce_FlexItem.h" #include "layout/juce_FlexBox.h" +#include "layout/juce_GridItem.h" +#include "layout/juce_Grid.h" + +constexpr Grid::Px operator"" _px (long double px) { return Grid::Px { px }; } +constexpr Grid::Px operator"" _px (unsigned long long px) { return Grid::Px { px }; } + +constexpr Grid::Fr operator"" _fr (unsigned long long fr) { return Grid::Fr { fr }; } + #endif } diff --git a/modules/juce_gui_basics/layout/juce_Grid.cpp b/modules/juce_gui_basics/layout/juce_Grid.cpp new file mode 100644 index 0000000000..87ecce0fa7 --- /dev/null +++ b/modules/juce_gui_basics/layout/juce_Grid.cpp @@ -0,0 +1,1020 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2017 - ROLI Ltd. + + JUCE is an open source library subject to commercial or open-source + licensing. + + By using JUCE, you agree to the terms of both the JUCE 5 End-User License + Agreement and JUCE 5 Privacy Policy (both updated and effective as of the + 27th April 2017). + + End User License Agreement: www.juce.com/juce-5-licence + Privacy Policy: www.juce.com/juce-5-privacy-policy + + Or: You may also use this code under the terms of the GPL v3 (see + www.gnu.org/licenses). + + JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER + EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE + DISCLAIMED. + + ============================================================================== +*/ + + +struct Grid::SizeCalculation +{ + static float getTotalAbsoluteSize (const juce::Array& tracks, Px gapSize) noexcept + { + float totalCellSize = 0.0f; + + for (const auto& trackInfo : tracks) + if (! trackInfo.isFraction || trackInfo.hasKeyword) + totalCellSize += trackInfo.size; + + float totalGap = tracks.size() > 1 ? static_cast ((tracks.size() - 1) * gapSize.pixels) + : 0.0f; + + return totalCellSize + totalGap; + } + + static float getRelativeUnitSize (float size, float totalAbsolute, const juce::Array& tracks) noexcept + { + const float totalRelative = juce::jlimit (0.0f, size, size - totalAbsolute); + float factorsSum = 0.0f; + + for (const auto& trackInfo : tracks) + if (trackInfo.isFraction) + factorsSum += trackInfo.size; + + jassert (factorsSum != 0.0f); + return totalRelative / factorsSum; + } + + //============================================================================== + static float getTotalAbsoluteHeight (const juce::Array& rowTracks, Px rowGap) + { + return getTotalAbsoluteSize (rowTracks, rowGap); + } + + static float getTotalAbsoluteWidth (const juce::Array& columnTracks, Px columnGap) + { + return getTotalAbsoluteSize (columnTracks, columnGap); + } + + static float getRelativeWidthUnit (float gridWidth, Px columnGap, const juce::Array& columnTracks) + { + return getRelativeUnitSize (gridWidth, getTotalAbsoluteWidth (columnTracks, columnGap), columnTracks); + } + + static float getRelativeHeightUnit (float gridHeight, Px rowGap, const juce::Array& rowTracks) + { + return getRelativeUnitSize (gridHeight, getTotalAbsoluteHeight (rowTracks, rowGap), rowTracks); + } + + //============================================================================== + static bool hasAnyFractions (const juce::Array& tracks) + { + for (auto& t : tracks) + if (t.isFraction) + return true; + + return false; + } + + void computeSizes (float gridWidth, float gridHeight, + Px columnGapToUse, Px rowGapToUse, + const juce::Array& columnTracks, + const juce::Array& rowTracks) + { + if (hasAnyFractions (columnTracks)) + relativeWidthUnit = getRelativeWidthUnit (gridWidth, columnGapToUse, columnTracks); + else + remainingWidth = gridWidth - getTotalAbsoluteSize (columnTracks, columnGapToUse); + + if (hasAnyFractions (rowTracks)) + relativeHeightUnit = getRelativeHeightUnit (gridHeight, rowGapToUse, rowTracks); + else + remainingHeight = gridHeight - getTotalAbsoluteSize (rowTracks, rowGapToUse); + } + + float relativeWidthUnit = 0.0f; + float relativeHeightUnit = 0.0f; + float remainingWidth = 0.0f; + float remainingHeight = 0.0f; +}; + +//============================================================================== +struct Grid::PlacementHelpers +{ + enum { invalid = -999999 }; + static constexpr auto emptyAreaCharacter = "."; + + //============================================================================== + struct LineRange { int start, end; }; + struct LineArea { LineRange column, row; }; + struct LineInfo { juce::StringArray lineNames; }; + + struct NamedArea + { + juce::String name; + LineArea lines; + }; + + //============================================================================== + static juce::Array getArrayOfLinesFromTracks (const juce::Array& tracks) + { + // fill line info array + juce::Array lines; + + for (int i = 1; i <= tracks.size(); ++i) + { + const auto& currentTrack = tracks.getReference (i - 1); + + if (i == 1) // start line + { + LineInfo li; + li.lineNames.add (currentTrack.startLineName); + lines.add (li); + } + + if (i > 1 && i <= tracks.size()) // two lines in between tracks + { + const auto& prevTrack = tracks.getReference (i - 2); + + LineInfo li; + li.lineNames.add (prevTrack.endLineName); + li.lineNames.add (currentTrack.startLineName); + + lines.add (li); + } + + if (i == tracks.size()) // end line + { + LineInfo li; + li.lineNames.add (currentTrack.endLineName); + lines.add (li); + } + } + + jassert (lines.size() == tracks.size() + 1); + + return lines; + } + + //============================================================================== + static int deduceAbsoluteLineNumberFromLineName (GridItem::Property prop, + const juce::Array& tracks) + { + jassert (prop.hasAbsolute()); + + const auto lines = getArrayOfLinesFromTracks (tracks); + int count = 0; + + for (int i = 0; i < lines.size(); i++) + { + for (const auto& name : lines.getReference (i).lineNames) + { + if (prop.name == name) + { + ++count; + break; + } + } + + if (count == prop.number) + return i + 1; + } + + jassertfalse; + return count; + } + + static int deduceAbsoluteLineNumber (GridItem::Property prop, + const juce::Array& tracks) + { + jassert (prop.hasAbsolute()); + + if (prop.hasName()) + return deduceAbsoluteLineNumberFromLineName (prop, tracks); + + return prop.number > 0 ? prop.number : tracks.size() + 2 + prop.number; + } + + static int deduceAbsoluteLineNumberFromNamedSpan (int startLineNumber, + GridItem::Property propertyWithSpan, + const juce::Array& tracks) + { + jassert (propertyWithSpan.hasSpan()); + + const auto lines = getArrayOfLinesFromTracks (tracks); + int count = 0; + + for (int i = startLineNumber; i < lines.size(); i++) + { + for (const auto& name : lines.getReference (i).lineNames) + { + if (propertyWithSpan.name == name) + { + ++count; + break; + } + } + + if (count == propertyWithSpan.number) + return i + 1; + } + + jassertfalse; + return count; + } + + static int deduceAbsoluteLineNumberBasedOnSpan (int startLineNumber, + GridItem::Property propertyWithSpan, + const juce::Array& tracks) + { + jassert (propertyWithSpan.hasSpan()); + + if (propertyWithSpan.hasName()) + return deduceAbsoluteLineNumberFromNamedSpan (startLineNumber, propertyWithSpan, tracks); + + return startLineNumber + propertyWithSpan.number; + } + + //============================================================================== + static LineRange deduceLineRange (GridItem::StartAndEndProperty prop, const juce::Array& tracks) + { + LineRange s; + + jassert (! (prop.start.hasAuto() && prop.end.hasAuto())); + + if (prop.start.hasAbsolute() && prop.end.hasAuto()) + { + prop.end = GridItem::Span (1); + } + else if (prop.start.hasAuto() && prop.end.hasAbsolute()) + { + prop.start = GridItem::Span (1); + } + + if (prop.start.hasAbsolute() && prop.end.hasAbsolute()) + { + s.start = deduceAbsoluteLineNumber (prop.start, tracks); + s.end = deduceAbsoluteLineNumber (prop.end, tracks); + } + else if (prop.start.hasAbsolute() && prop.end.hasSpan()) + { + s.start = deduceAbsoluteLineNumber (prop.start, tracks); + s.end = deduceAbsoluteLineNumberBasedOnSpan (s.start, prop.end, tracks); + } + else if (prop.start.hasSpan() && prop.end.hasAbsolute()) + { + s.start = deduceAbsoluteLineNumber (prop.end, tracks); + s.end = deduceAbsoluteLineNumberBasedOnSpan (s.start, prop.start, tracks); + } + else + { + // Can't have an item with spans on both start and end. + jassertfalse; + s.start = s.end = {}; + } + + // swap if start overtakes end + if (s.start > s.end) + std::swap (s.start, s.end); + else if (s.start == s.end) + s.end = s.start + 1; + + return s; + } + + static LineArea deduceLineArea (const GridItem& item, + const Grid& grid, + const std::map& namedAreas) + { + if (item.area.isNotEmpty() && ! grid.templateAreas.isEmpty()) + return namedAreas.at (item.area); + + return { deduceLineRange (item.column, grid.templateColumns), + deduceLineRange (item.row, grid.templateRows) }; + } + + //============================================================================== + static juce::Array parseAreasProperty (const juce::StringArray& areasStrings) + { + juce::Array strings; + + for (const auto& areaString : areasStrings) + strings.add (juce::StringArray::fromTokens (areaString, false)); + + for (auto s : strings) + jassert (s.size() == strings[0].size()); // all rows must have the same number of columns + + return strings; + } + + static NamedArea findArea (juce::Array& stringsArrays) + { + NamedArea area; + + for (auto& stringArray : stringsArrays) + { + for (auto& string : stringArray) + { + // find anchor + if (area.name.isEmpty()) + { + if (string != emptyAreaCharacter) + { + area.name = string; + area.lines.row.start = stringsArrays.indexOf (stringArray) + 1; // non-zero indexed; + area.lines.column.start = stringArray.indexOf (string) + 1; // non-zero indexed; + + area.lines.row.end = stringsArrays.indexOf (stringArray) + 2; + area.lines.column.end = stringArray.indexOf (string) + 2; + + // mark as visited + string = emptyAreaCharacter; + } + } + else + { + if (string == emptyAreaCharacter) + { + break; + } + else if (string == area.name) + { + area.lines.row.end = stringsArrays.indexOf (stringArray) + 2; + area.lines.column.end = stringArray.indexOf (string) + 2; + + // mark as visited + string = emptyAreaCharacter; + } + } + } + } + + return area; + } + + //============================================================================== + static std::map deduceNamedAreas (const juce::StringArray& areasStrings) + { + auto stringsArrays = parseAreasProperty (areasStrings); + + std::map areas; + + for (auto area = findArea (stringsArrays); area.name.isNotEmpty(); area = findArea (stringsArrays)) + { + if (areas.count (area.name) == 0) + areas[area.name] = area.lines; + else + // Make sure your template-areas property only has one area with the same name and is well-formed + jassertfalse; + } + + return areas; + } + + //============================================================================== + static float getCoord (int trackNumber, float relativeUnit, Px gap, const juce::Array& tracks) + { + float c = 0; + + for (const auto* it = tracks.begin(); it != tracks.begin() + trackNumber - 1; ++it) + c += (it->isFraction ? it->size * relativeUnit : it->size) + static_cast (gap.pixels); + + return c; + } + + static juce::Rectangle getCellBounds (int columnNumber, int rowNumber, + const juce::Array& columnTracks, + const juce::Array& rowTracks, + Grid::SizeCalculation calculation, + Px columnGap, Px rowGap) + { + jassert (columnNumber >= 1 && columnNumber <= columnTracks.size()); + jassert (rowNumber >= 1 && rowNumber <= rowTracks.size()); + + const auto x = getCoord (columnNumber, calculation.relativeWidthUnit, columnGap, columnTracks); + const auto y = getCoord (rowNumber, calculation.relativeHeightUnit, rowGap, rowTracks); + + const auto& columnTrackInfo = columnTracks.getReference (columnNumber - 1); + const float width = columnTrackInfo.isFraction ? columnTrackInfo.size * calculation.relativeWidthUnit + : columnTrackInfo.size; + + const auto& rowTrackInfo = rowTracks.getReference (rowNumber - 1); + const float height = rowTrackInfo.isFraction ? rowTrackInfo.size * calculation.relativeHeightUnit + : rowTrackInfo.size; + + return { x, y, width, height }; + } + + static juce::Rectangle alignCell (juce::Rectangle area, + int columnNumber, int rowNumber, + int numberOfColumns, int numberOfRows, + Grid::SizeCalculation calculation, + Grid::AlignContent alignContent, + Grid::JustifyContent justifyContent) + { + if (alignContent == Grid::AlignContent::end) + area.setY (area.getY() + calculation.remainingHeight); + + if (justifyContent == Grid::JustifyContent::end) + area.setX (area.getX() + calculation.remainingWidth); + + if (alignContent == Grid::AlignContent::center) + area.setY (area.getY() + calculation.remainingHeight / 2); + + if (justifyContent == Grid::JustifyContent::center) + area.setX (area.getX() + calculation.remainingWidth / 2); + + if (alignContent == Grid::AlignContent::spaceBetween) + { + const auto shift = ((rowNumber - 1) * (calculation.remainingHeight / float(numberOfRows - 1))); + area.setY (area.getY() + shift); + } + + if (justifyContent == Grid::JustifyContent::spaceBetween) + { + const auto shift = ((columnNumber - 1) * (calculation.remainingWidth / float(numberOfColumns - 1))); + area.setX (area.getX() + shift); + } + + if (alignContent == Grid::AlignContent::spaceEvenly) + { + const auto shift = (rowNumber * (calculation.remainingHeight / float(numberOfRows + 1))); + area.setY (area.getY() + shift); + } + + if (justifyContent == Grid::JustifyContent::spaceEvenly) + { + const auto shift = (columnNumber * (calculation.remainingWidth / float(numberOfColumns + 1))); + area.setX (area.getX() + shift); + } + + if (alignContent == Grid::AlignContent::spaceAround) + { + const auto inbetweenShift = calculation.remainingHeight / float(numberOfRows); + const auto sidesShift = inbetweenShift / 2; + auto shift = (rowNumber - 1) * inbetweenShift + sidesShift; + + area.setY (area.getY() + shift); + } + + if (justifyContent == Grid::JustifyContent::spaceAround) + { + const auto inbetweenShift = calculation.remainingWidth / float(numberOfColumns); + const auto sidesShift = inbetweenShift / 2; + auto shift = (columnNumber - 1) * inbetweenShift + sidesShift; + + area.setX (area.getX() + shift); + } + + return area; + } + + static juce::Rectangle getAreaBounds (int columnLineNumberStart, int columnLineNumberEnd, + int rowLineNumberStart, int rowLineNumberEnd, + const juce::Array& columnTracks, + const juce::Array& rowTracks, + Grid::SizeCalculation calculation, + Grid::AlignContent alignContent, + Grid::JustifyContent justifyContent, + Px columnGap, Px rowGap) + { + auto startCell = getCellBounds (columnLineNumberStart, rowLineNumberStart, + columnTracks, rowTracks, + calculation, + columnGap, rowGap); + + auto endCell = getCellBounds (columnLineNumberEnd - 1, rowLineNumberEnd - 1, + columnTracks, rowTracks, + calculation, + columnGap, rowGap); + + startCell = alignCell (startCell, + columnLineNumberStart, rowLineNumberStart, + columnTracks.size(), rowTracks.size(), + calculation, + alignContent, + justifyContent); + + endCell = alignCell (endCell, + columnLineNumberEnd - 1, rowLineNumberEnd - 1, + columnTracks.size(), rowTracks.size(), + calculation, + alignContent, + justifyContent); + + return startCell.getUnion (endCell); + } +}; + +//============================================================================== +struct Grid::AutoPlacement +{ + using ItemPlacementArray = juce::Array>; + + //============================================================================== + struct OccupancyPlane + { + struct Cell { int column, row; }; + + OccupancyPlane (int highestColumnToUse, int highestRowToUse, bool isColoumnFirst) + : highestCrossDimension (isColoumnFirst ? highestRowToUse : highestColumnToUse), + columnFirst (isColoumnFirst) + {} + + Grid::PlacementHelpers::LineArea setCell (Cell cell, int columnSpan, int rowSpan) + { + for (int i = 0; i < columnSpan; i++) + for (int j = 0; j < rowSpan; j++) + setCell (cell.column + i, cell.row + j); + + return { { cell.column, cell.column + columnSpan }, { cell.row, cell.row + rowSpan } }; + } + + Grid::PlacementHelpers::LineArea setCell (Cell start, Cell end) + { + return setCell (start, std::abs (end.column - start.column), + std::abs (end.row - start.row)); + } + + Cell nextAvailable (Cell referenceCell, int columnSpan, int rowSpan) + { + while (isOccupied (referenceCell, columnSpan, rowSpan) || isOutOfBounds (referenceCell, columnSpan, rowSpan)) + referenceCell = advance (referenceCell); + + return referenceCell; + } + + Cell nextAvailableOnRow (Cell referenceCell, int columnSpan, int rowSpan, int rowNumber) + { + if (columnFirst && (rowNumber + rowSpan) > highestCrossDimension) + highestCrossDimension = rowNumber + rowSpan; + + while (isOccupied (referenceCell, columnSpan, rowSpan) + || (referenceCell.row != rowNumber)) + referenceCell = advance (referenceCell); + + return referenceCell; + } + + Cell nextAvailableOnColumn (Cell referenceCell, int columnSpan, int rowSpan, int columnNumber) + { + if (! columnFirst && (columnNumber + columnSpan) > highestCrossDimension) + highestCrossDimension = columnNumber + columnSpan; + + while (isOccupied (referenceCell, columnSpan, rowSpan) + || (referenceCell.column != columnNumber)) + referenceCell = advance (referenceCell); + + return referenceCell; + } + + private: + struct SortableCell + { + int column, row; + bool columnFirst; + + bool operator< (const SortableCell& other) const + { + if (columnFirst) + { + if (row == other.row) + return column < other.column; + + return row < other.row; + } + + if (row == other.row) + return column < other.column; + + return row < other.row; + } + }; + + void setCell (int column, int row) + { + occupiedCells.insert ({ column, row, columnFirst }); + } + + bool isOccupied (Cell cell) const + { + return occupiedCells.count ({ cell.column, cell.row, columnFirst }) > 0; + } + + bool isOccupied (Cell cell, int columnSpan, int rowSpan) const + { + for (int i = 0; i < columnSpan; i++) + for (int j = 0; j < rowSpan; j++) + if (isOccupied ({ cell.column + i, cell.row + j })) + return true; + + return false; + } + + bool isOutOfBounds (Cell cell, int columnSpan, int rowSpan) const + { + const auto crossSpan = columnFirst ? rowSpan : columnSpan; + + return (getCrossDimension (cell) + crossSpan) > getHighestCrossDimension(); + } + + int getHighestCrossDimension() const + { + return std::max (getCrossDimension ({ occupiedCells.crbegin()->column, occupiedCells.crbegin()->row }), + highestCrossDimension); + } + + Cell advance (Cell cell) const + { + if ((getCrossDimension (cell) + 1) >= getHighestCrossDimension()) + return fromDimensions (getMainDimension (cell) + 1, 1); + + return fromDimensions (getMainDimension (cell), getCrossDimension (cell) + 1); + } + + int getMainDimension (Cell cell) const { return columnFirst ? cell.column : cell.row; } + int getCrossDimension (Cell cell) const { return columnFirst ? cell.row : cell.column; } + + Cell fromDimensions (int mainDimension, int crossDimension) const + { + if (columnFirst) + return { mainDimension, crossDimension }; + + return { crossDimension, mainDimension }; + } + + int highestCrossDimension; + bool columnFirst; + std::set occupiedCells; + }; + + //============================================================================== + static bool isFixed (GridItem::StartAndEndProperty prop) + { + return prop.start.hasName() || prop.start.hasAbsolute() || prop.end.hasName() || prop.end.hasAbsolute(); + } + + static bool hasFullyFixedPlacement (const GridItem& item) + { + if (item.area.isNotEmpty()) + return true; + + if (isFixed (item.column) && isFixed (item.row)) + return true; + + return false; + } + + static bool hasPartialFixedPlacement (const GridItem& item) + { + if (item.area.isNotEmpty()) + return false; + + if (isFixed (item.column) ^ isFixed (item.row)) + return true; + + return false; + } + + static bool hasAutoPlacement (const GridItem& item) + { + return ! hasFullyFixedPlacement (item) && ! hasPartialFixedPlacement (item); + } + + //============================================================================== + static bool hasDenseAutoFlow (Grid::AutoFlow autoFlow) + { + return autoFlow == Grid::AutoFlow::columnDense + || autoFlow == Grid::AutoFlow::rowDense; + } + + static bool isColumnAutoFlow (Grid::AutoFlow autoFlow) + { + return autoFlow == Grid::AutoFlow::column + || autoFlow == Grid::AutoFlow::columnDense; + } + + //============================================================================== + static int getSpanFromAuto (GridItem::StartAndEndProperty prop) + { + if (prop.end.hasSpan()) + return prop.end.number; + + if (prop.start.hasSpan()) + return prop.start.number; + + return 1; + } + + //============================================================================== + ItemPlacementArray deduceAllItems (Grid& grid) const + { + const auto namedAreas = Grid::PlacementHelpers::deduceNamedAreas (grid.templateAreas); + + OccupancyPlane plane (juce::jmax (grid.templateColumns.size() + 1, 2), + juce::jmax (grid.templateRows.size() + 1, 2), + isColumnAutoFlow (grid.autoFlow)); + + ItemPlacementArray itemPlacementArray; + juce::Array sortedItems; + + for (auto& item : grid.items) + sortedItems.add (&item); + + sortedItems.sort (*this, true); + + // place fixed items first + for (auto* item : sortedItems) + { + if (hasFullyFixedPlacement (*item)) + { + const auto a = Grid::PlacementHelpers::deduceLineArea (*item, grid, namedAreas); + plane.setCell ({ a.column.start, a.row.start }, { a.column.end, a.row.end }); + itemPlacementArray.add ({ item, a }); + } + } + + OccupancyPlane::Cell lastInsertionCell = { 1, 1 }; + + for (auto* item : sortedItems) + { + if (hasPartialFixedPlacement (*item)) + { + if (isFixed (item->column)) + { + const auto p = Grid::PlacementHelpers::deduceLineRange (item->column, grid.templateColumns); + const auto columnSpan = std::abs (p.start - p.end); + const auto rowSpan = getSpanFromAuto (item->row); + + const auto insertionCell = hasDenseAutoFlow (grid.autoFlow) ? OccupancyPlane::Cell { p.start, 1 } + : lastInsertionCell; + const auto nextAvailableCell = plane.nextAvailableOnColumn (insertionCell, columnSpan, rowSpan, p.start); + const auto lineArea = plane.setCell (nextAvailableCell, columnSpan, rowSpan); + lastInsertionCell = nextAvailableCell; + + itemPlacementArray.add ({ item, lineArea }); + } + else if (isFixed (item->row)) + { + const auto p = Grid::PlacementHelpers::deduceLineRange (item->row, grid.templateRows); + const auto columnSpan = getSpanFromAuto (item->column); + const auto rowSpan = std::abs (p.start - p.end); + + const auto insertionCell = hasDenseAutoFlow (grid.autoFlow) ? OccupancyPlane::Cell { 1, p.start } + : lastInsertionCell; + + const auto nextAvailableCell = plane.nextAvailableOnRow (insertionCell, columnSpan, rowSpan, p.start); + const auto lineArea = plane.setCell (nextAvailableCell, columnSpan, rowSpan); + + lastInsertionCell = nextAvailableCell; + + itemPlacementArray.add ({ item, lineArea }); + } + } + } + + lastInsertionCell = { 1, 1 }; + + for (auto* item : sortedItems) + { + if (hasAutoPlacement (*item)) + { + const auto columnSpan = getSpanFromAuto (item->column); + const auto rowSpan = getSpanFromAuto (item->row); + + const auto nextAvailableCell = plane.nextAvailable (lastInsertionCell, columnSpan, rowSpan); + const auto lineArea = plane.setCell (nextAvailableCell, columnSpan, rowSpan); + + if (! hasDenseAutoFlow (grid.autoFlow)) + lastInsertionCell = nextAvailableCell; + + itemPlacementArray.add ({ item, lineArea }); + } + } + + return itemPlacementArray; + } + + //============================================================================== + static std::pair getHighestEndLinesNumbers (const ItemPlacementArray& items) + { + int columnEndLine = 1; + int rowEndLine = 1; + + for (auto& item : items) + { + const auto p = item.second; + columnEndLine = std::max (p.column.end, columnEndLine); + rowEndLine = std::max (p.row.end, rowEndLine); + } + + return { columnEndLine, rowEndLine }; + } + + static std::pair, juce::Array> createImplicitTracks (const Grid& grid, + const ItemPlacementArray& items) + { + const auto columnAndRowLineEnds = getHighestEndLinesNumbers (items); + + juce::Array implicitColumnTracks, implicitRowTracks; + + for (int i = grid.templateColumns.size() + 1; i < columnAndRowLineEnds.first; i++) + implicitColumnTracks.add (grid.autoColumns); + + for (int i = grid.templateRows.size() + 1; i < columnAndRowLineEnds.second; i++) + implicitRowTracks.add (grid.autoRows); + + return { implicitColumnTracks, implicitRowTracks }; + } + + //============================================================================== + static int compareElements (const GridItem* i1, const GridItem* i2) noexcept + { + return i1->order < i2->order ? -1 : (i2->order < i1->order ? 1 : 0); + } + + //============================================================================== + static void applySizeForAutoTracks (juce::Array& columns, + juce::Array& rows, + const ItemPlacementArray& itemPlacementArray) + { + auto isSpan = [](Grid::PlacementHelpers::LineRange r) -> bool { return std::abs (r.end - r.start) > 1; }; + + auto getHighestItemOnRow = [isSpan](int rowNumber, const ItemPlacementArray& itemPlacementArrayToUse) -> float + { + float highestRowSize = 0.0f; + + for (const auto& i : itemPlacementArrayToUse) + if (! isSpan (i.second.row) && i.second.row.start == rowNumber) + highestRowSize = std::max (highestRowSize, i.first->height + i.first->margin.top + i.first->margin.bottom); + + return highestRowSize; + }; + + auto getHighestItemOnColumn = [isSpan](int rowNumber, const ItemPlacementArray& itemPlacementArrayToUse) -> float + { + float highestColumnSize = 0.0f; + for (const auto& i : itemPlacementArrayToUse) + if (! isSpan (i.second.column) && i.second.column.start == rowNumber) + highestColumnSize = std::max (highestColumnSize, i.first->width + i.first->margin.left + i.first->margin.right); + + return highestColumnSize; + }; + + for (int i = 0; i < rows.size(); i++) + if (rows.getReference (i).hasKeyword) + rows.getReference (i).size = getHighestItemOnRow (i + 1, itemPlacementArray); + + for (int i = 0; i < columns.size(); i++) + if (columns.getReference (i).hasKeyword) + columns.getReference (i).size = getHighestItemOnColumn (i + 1, itemPlacementArray); + } +}; + +//============================================================================== +struct Grid::BoxAlignment +{ + static juce::Rectangle alignItem (const GridItem& item, + const Grid& grid, + juce::Rectangle area) + { + // if item align is auto, inherit value from grid + Grid::AlignItems alignType = Grid::AlignItems::start; + Grid::JustifyItems justifyType = Grid::JustifyItems::start; + + if (item.alignSelf == GridItem::AlignSelf::autoValue) + alignType = grid.alignItems; + else + alignType = static_cast (item.alignSelf); + + if (item.justifySelf == GridItem::JustifySelf::autoValue) + justifyType = grid.justifyItems; + else + justifyType = static_cast (item.justifySelf); + + // subtract margin from area + area = juce::BorderSize (item.margin.top, item.margin.left, item.margin.bottom, item.margin.right) + .subtractedFrom (area); + + // align and justify + auto r = area; + + if (item.width != GridItem::notAssigned) + r.setWidth (item.width); + + if (item.height != GridItem::notAssigned) + r.setHeight (item.height); + + if (alignType == Grid::AlignItems::start && justifyType == Grid::JustifyItems::start) + return r; + + if (alignType == Grid::AlignItems::end) + r.setY (r.getY() + (area.getHeight() - r.getHeight())); + + if (justifyType == Grid::JustifyItems::end) + r.setX (r.getX() + (area.getWidth() - r.getWidth())); + + if (alignType == Grid::AlignItems::center) + r.setCentre (r.getCentreX(), area.getCentreY()); + + if (justifyType == Grid::JustifyItems::center) + r.setCentre (area.getCentreX(), r.getCentreY()); + + return r; + } +}; + +//============================================================================== +Grid::TrackInfo::TrackInfo() noexcept : hasKeyword (true) {} + +Grid::TrackInfo::TrackInfo (Px sizeInPixels) noexcept : size (static_cast (sizeInPixels.pixels)), isFraction (false) {} + +Grid::TrackInfo::TrackInfo (Fr fractionOfFreeSpace) noexcept : size ((float)fractionOfFreeSpace.fraction), isFraction (true) {} + +Grid::TrackInfo::TrackInfo (Px sizeInPixels, const juce::String& endLineNameToUse) noexcept : Grid::TrackInfo (sizeInPixels) +{ + endLineName = endLineNameToUse; +} + +Grid::TrackInfo::TrackInfo (Fr fractionOfFreeSpace, const juce::String& endLineNameToUse) noexcept : Grid::TrackInfo (fractionOfFreeSpace) +{ + endLineName = endLineNameToUse; +} + +Grid::TrackInfo::TrackInfo (const juce::String& startLineNameToUse, Px sizeInPixels) noexcept : Grid::TrackInfo (sizeInPixels) +{ + startLineName = startLineNameToUse; +} + +Grid::TrackInfo::TrackInfo (const juce::String& startLineNameToUse, Fr fractionOfFreeSpace) noexcept : Grid::TrackInfo (fractionOfFreeSpace) +{ + startLineName = startLineNameToUse; +} + +Grid::TrackInfo::TrackInfo (const juce::String& startLineNameToUse, Px sizeInPixels, const juce::String& endLineNameToUse) noexcept + : Grid::TrackInfo (startLineNameToUse, sizeInPixels) +{ + endLineName = endLineNameToUse; +} + +Grid::TrackInfo::TrackInfo (const juce::String& startLineNameToUse, Fr fractionOfFreeSpace, const juce::String& endLineNameToUse) noexcept + : Grid::TrackInfo (startLineNameToUse, fractionOfFreeSpace) +{ + endLineName = endLineNameToUse; +} + +//============================================================================== +Grid::Grid() noexcept {} +Grid::~Grid() noexcept {} + +//============================================================================== +void Grid::performLayout (juce::Rectangle targetArea) +{ + const auto itemsAndAreas = Grid::AutoPlacement().deduceAllItems (*this); + + const auto implicitTracks = Grid::AutoPlacement::createImplicitTracks (*this, itemsAndAreas); + auto columnTracks = templateColumns; + auto rowTracks = templateRows; + columnTracks.addArray (implicitTracks.first); + rowTracks.addArray (implicitTracks.second); + + Grid::AutoPlacement::applySizeForAutoTracks (columnTracks, rowTracks, itemsAndAreas); + + Grid::SizeCalculation calculation; + calculation.computeSizes (targetArea.toFloat().getWidth(), + targetArea.toFloat().getHeight(), + columnGap, + rowGap, + columnTracks, + rowTracks); + + for (auto& itemAndArea : itemsAndAreas) + { + const auto a = itemAndArea.second; + const auto areaBounds = Grid::PlacementHelpers::getAreaBounds (a.column.start, a.column.end, + a.row.start, a.row.end, + columnTracks, + rowTracks, + calculation, + alignContent, + justifyContent, + columnGap, + rowGap); + + auto* item = itemAndArea.first; + item->currentBounds = Grid::BoxAlignment::alignItem (*item, *this, areaBounds) + + targetArea.toFloat().getPosition(); + + if (auto* c = item->associatedComponent) + c->setBounds (item->currentBounds.toNearestInt()); + } +} diff --git a/modules/juce_gui_basics/layout/juce_Grid.h b/modules/juce_gui_basics/layout/juce_Grid.h new file mode 100644 index 0000000000..21d8cdbd42 --- /dev/null +++ b/modules/juce_gui_basics/layout/juce_Grid.h @@ -0,0 +1,174 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2017 - ROLI Ltd. + + JUCE is an open source library subject to commercial or open-source + licensing. + + By using JUCE, you agree to the terms of both the JUCE 5 End-User License + Agreement and JUCE 5 Privacy Policy (both updated and effective as of the + 27th April 2017). + + End User License Agreement: www.juce.com/juce-5-licence + Privacy Policy: www.juce.com/juce-5-privacy-policy + + Or: You may also use this code under the terms of the GPL v3 (see + www.gnu.org/licenses). + + JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER + EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE + DISCLAIMED. + + ============================================================================== +*/ + + +/** + Container that handles geometry for grid layouts (fixed columns and rows) using a set of declarative rules. + + Implemented from the `CSS Grid Layout` specification as described at: + https://css-tricks.com/snippets/css/complete-guide-grid/ + + @see GridItem +*/ +class JUCE_API Grid +{ +public: + //============================================================================== + /** A size in pixels */ + struct Px + { + explicit Px (float p) : pixels (static_cast(p)) { /*sta (p >= 0.0f);*/ } + explicit Px (int p) : pixels (static_cast(p)) { /*sta (p >= 0.0f);*/ } + explicit constexpr Px (long double p) : pixels (p) {} + explicit constexpr Px (unsigned long long p) : pixels (static_cast(p)) {} + + long double pixels; + }; + + /** A fractional ratio integer */ + struct Fr + { + explicit Fr (int f) : fraction (static_cast (f)) {} + explicit constexpr Fr (unsigned long long p) : fraction (p) {} + + unsigned long long fraction; + }; + + //============================================================================== + /** */ + struct TrackInfo + { + /** Creates a track with auto dimension. */ + TrackInfo() noexcept; + /** */ + TrackInfo (Px sizeInPixels) noexcept; + /** */ + TrackInfo (Fr fractionOfFreeSpace) noexcept; + + /** */ + TrackInfo (Px sizeInPixels, const juce::String& endLineNameToUse) noexcept; + /** */ + TrackInfo (Fr fractionOfFreeSpace, const juce::String& endLineNameToUse) noexcept; + + /** */ + TrackInfo (const juce::String& startLineNameToUse, Px sizeInPixels) noexcept; + /** */ + TrackInfo (const juce::String& startLineNameToUse, Fr fractionOfFreeSpace) noexcept; + + /** */ + TrackInfo (const juce::String& startLineNameToUse, Px sizeInPixels, const juce::String& endLineNameToUse) noexcept; + /** */ + TrackInfo (const juce::String& startLineNameToUse, Fr fractionOfFreeSpace, const juce::String& endLineNameToUse) noexcept; + + private: + friend class Grid; + friend class GridItem; + + float size = 0; // Either a fraction or an absolute size in pixels + bool isFraction = false; + bool hasKeyword = false; + + juce::String startLineName, endLineName; + }; + + //============================================================================== + /** */ + enum class JustifyItems : int { start = 0, end, center, stretch }; + /** */ + enum class AlignItems : int { start = 0, end, center, stretch }; + /** */ + enum class JustifyContent { start, end, center, stretch, spaceAround, spaceBetween, spaceEvenly }; + /** */ + enum class AlignContent { start, end, center, stretch, spaceAround, spaceBetween, spaceEvenly }; + /** */ + enum class AutoFlow { row, column, rowDense, columnDense }; + + + //============================================================================== + /** */ + Grid() noexcept; + + /** Destructor */ + ~Grid() noexcept; + + //============================================================================== + /** */ + JustifyItems justifyItems = JustifyItems::stretch; + /** */ + AlignItems alignItems = AlignItems::stretch; + /** */ + JustifyContent justifyContent = JustifyContent::stretch; + /** */ + AlignContent alignContent = AlignContent::stretch; + /** */ + AutoFlow autoFlow = AutoFlow::row; + + + //============================================================================== + /** */ + juce::Array templateColumns; + + /** */ + juce::Array templateRows; + + /** Template areas */ + juce::StringArray templateAreas; + + /** */ + TrackInfo autoRows; + + /** */ + TrackInfo autoColumns; + + /** */ + Px columnGap { 0 }; + /** */ + Px rowGap { 0 }; + + /** */ + void setGap (Px sizeInPixels) noexcept { rowGap = columnGap = sizeInPixels; } + + //============================================================================== + /** */ + juce::Array items; + + //============================================================================== + /** */ + void performLayout (juce::Rectangle); + + //============================================================================== + /** */ + int getNumberOfColumns() const noexcept { return templateColumns.size(); } + /** */ + int getNumberOfRows() const noexcept { return templateRows.size(); } + +private: + //============================================================================== + struct SizeCalculation; + struct PlacementHelpers; + struct AutoPlacement; + struct BoxAlignment; +}; diff --git a/modules/juce_gui_basics/layout/juce_GridItem.cpp b/modules/juce_gui_basics/layout/juce_GridItem.cpp new file mode 100644 index 0000000000..ce613ed380 --- /dev/null +++ b/modules/juce_gui_basics/layout/juce_GridItem.cpp @@ -0,0 +1,176 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2017 - ROLI Ltd. + + JUCE is an open source library subject to commercial or open-source + licensing. + + By using JUCE, you agree to the terms of both the JUCE 5 End-User License + Agreement and JUCE 5 Privacy Policy (both updated and effective as of the + 27th April 2017). + + End User License Agreement: www.juce.com/juce-5-licence + Privacy Policy: www.juce.com/juce-5-privacy-policy + + Or: You may also use this code under the terms of the GPL v3 (see + www.gnu.org/licenses). + + JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER + EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE + DISCLAIMED. + + ============================================================================== +*/ + + +GridItem::Property::Property() noexcept : isAuto (true) +{ +} + +GridItem::Property::Property (GridItem::Keyword keyword) noexcept : isAuto (keyword == GridItem::Keyword::autoValue) +{ + jassert (keyword == GridItem::Keyword::autoValue); +} + +GridItem::Property::Property (const char* lineNameToUse) noexcept : GridItem::Property (juce::String (lineNameToUse)) +{ +} + +GridItem::Property::Property (const juce::String& lineNameToUse) noexcept : name (lineNameToUse), number (1) +{ +} + +GridItem::Property::Property (int numberToUse) noexcept : number (numberToUse) +{ +} + +GridItem::Property::Property (int numberToUse, const juce::String& lineNameToUse) noexcept + : name (lineNameToUse), number (numberToUse) +{ +} + +GridItem::Property::Property (Span spanToUse) noexcept + : name (spanToUse.name), number (spanToUse.number), isSpan (true) +{ +} + + +//============================================================================== +GridItem::Margin::Margin() noexcept : left(), right(), top(), bottom() +{} + +GridItem::Margin::Margin (float v) noexcept : left (v), right (v), top (v), bottom (v) +{} + +//============================================================================== +GridItem::GridItem() noexcept {} +GridItem::~GridItem() noexcept {} + +GridItem::GridItem (juce::Component& componentToUse) noexcept : associatedComponent (&componentToUse) {} +GridItem::GridItem (juce::Component* componentToUse) noexcept : associatedComponent (componentToUse) {} + +void GridItem::setArea (Property rowStart, Property columnStart, Property rowEnd, Property columnEnd) +{ + column.start = columnStart; + column.end = columnEnd; + row.start = rowStart; + row.end = rowEnd; +} + +void GridItem::setArea (Property rowStart, Property columnStart) +{ + column.start = columnStart; + row.start = rowStart; +} + +void GridItem::setArea (const juce::String& areaName) +{ + area = areaName; +} + +GridItem GridItem::withArea (Property rowStart, Property columnStart, Property rowEnd, Property columnEnd) const noexcept +{ + auto gi = *this; + gi.setArea (rowStart, columnStart, rowEnd, columnEnd); + return gi; +} + +GridItem GridItem::withArea (Property rowStart, Property columnStart) const noexcept +{ + auto gi = *this; + gi.setArea (rowStart, columnStart); + return gi; +} + +GridItem GridItem::withArea (const juce::String& areaName) const noexcept +{ + auto gi = *this; + gi.setArea (areaName); + return gi; +} + +GridItem GridItem::withRow (StartAndEndProperty newRow) const noexcept +{ + auto gi = *this; + gi.row = newRow; + return gi; +} + +GridItem GridItem::withColumn (StartAndEndProperty newColumn) const noexcept +{ + auto gi = *this; + gi.column = newColumn; + return gi; +} + +GridItem GridItem::withAlignSelf (AlignSelf newAlignSelf) const noexcept +{ + auto gi = *this; + gi.alignSelf = newAlignSelf; + return gi; +} + +GridItem GridItem::withJustifySelf (JustifySelf newJustifySelf) const noexcept +{ + auto gi = *this; + gi.justifySelf = newJustifySelf; + return gi; +} + +GridItem GridItem::withWidth (float newWidth) const noexcept +{ + auto gi = *this; + gi.width = newWidth; + return gi; +} + +GridItem GridItem::withHeight (float newHeight) const noexcept +{ + auto gi = *this; + gi.height = newHeight; + return gi; +} + +GridItem GridItem::withSize (float newWidth, float newHeight) const noexcept +{ + auto gi = *this; + gi.width = newWidth; + gi.height = newHeight; + return gi; +} + +GridItem GridItem::withMargin (Margin newHeight) const noexcept +{ + auto gi = *this; + gi.margin = newHeight; + return gi; +} + +GridItem GridItem::withOrder (int newOrder) const noexcept +{ + auto gi = *this; + gi.order = newOrder; + return gi; +} diff --git a/modules/juce_gui_basics/layout/juce_GridItem.h b/modules/juce_gui_basics/layout/juce_GridItem.h new file mode 100644 index 0000000000..e8d6c0267d --- /dev/null +++ b/modules/juce_gui_basics/layout/juce_GridItem.h @@ -0,0 +1,221 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2017 - ROLI Ltd. + + JUCE is an open source library subject to commercial or open-source + licensing. + + By using JUCE, you agree to the terms of both the JUCE 5 End-User License + Agreement and JUCE 5 Privacy Policy (both updated and effective as of the + 27th April 2017). + + End User License Agreement: www.juce.com/juce-5-licence + Privacy Policy: www.juce.com/juce-5-privacy-policy + + Or: You may also use this code under the terms of the GPL v3 (see + www.gnu.org/licenses). + + JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER + EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE + DISCLAIMED. + + ============================================================================== +*/ + + +/** + Defines an item in a Grid + @see Grid +*/ +class JUCE_API GridItem +{ +public: + enum class Keyword { autoValue }; + + //============================================================================== + /** */ + struct Span + { + explicit Span (int numberToUse) noexcept : number (numberToUse) + { + /* Span must be at least one and positive */ + jassert (numberToUse > 0); + } + + explicit Span (int numberToUse, const juce::String& nameToUse) : Span (numberToUse) + { + /* Name must not be empty */ + jassert (nameToUse.isNotEmpty()); + name = nameToUse; + } + + explicit Span (const juce::String& nameToUse) : name (nameToUse) + { + /* Name must not be empty */ + jassert (nameToUse.isNotEmpty()); + } + + int number = 1; + juce::String name; + }; + + //============================================================================== + /** */ + struct Property + { + /** */ + Property() noexcept; + + /** */ + Property (Keyword keyword) noexcept; + + /** */ + Property (const char* lineNameToUse) noexcept; + + /** */ + Property (const juce::String& lineNameToUse) noexcept; + + /** */ + Property (int numberToUse) noexcept; + + /** */ + Property (int numberToUse, const juce::String& lineNameToUse) noexcept; + + /** */ + Property (Span spanToUse) noexcept; + + private: + bool hasSpan() const noexcept { return isSpan && ! isAuto; } + bool hasAbsolute() const noexcept { return ! (isSpan || isAuto); } + bool hasAuto() const noexcept { return isAuto; } + bool hasName() const noexcept { return name.isNotEmpty(); } + + friend class Grid; + + juce::String name; + int number = 1; /** Either an absolute line number or number of lines to span across. */ + bool isSpan = false; + bool isAuto = false; + }; + + //============================================================================== + /** */ + struct StartAndEndProperty { Property start, end; }; + + //============================================================================== + /** */ + enum class JustifySelf : int { start = 0, end, center, stretch, autoValue }; + /** */ + enum class AlignSelf : int { start = 0, end, center, stretch, autoValue }; + + /** */ + GridItem() noexcept; + /** */ + GridItem (juce::Component& componentToUse) noexcept; + /** */ + GridItem (juce::Component* componentToUse) noexcept; + + /** Destructor. */ + ~GridItem() noexcept; + + //============================================================================== + /** */ + juce::Component* associatedComponent = nullptr; + + //============================================================================== + /** */ + int order = 0; + + /** */ + JustifySelf justifySelf = JustifySelf::autoValue; + /** */ + AlignSelf alignSelf = AlignSelf::autoValue; + + /** */ + StartAndEndProperty column = { Keyword::autoValue, Keyword::autoValue }; + /** */ + StartAndEndProperty row = { Keyword::autoValue, Keyword::autoValue }; + + /** */ + juce::String area; + + //============================================================================== + enum + { + useDefaultValue = -2, /* TODO: useDefaultValue should be named useAuto */ + notAssigned = -1 + }; + + /* TODO: move all of this into a common class that is shared with the FlexItem */ + float width = notAssigned; + float minWidth = 0; + float maxWidth = notAssigned; + + float height = notAssigned; + float minHeight = 0; + float maxHeight = notAssigned; + + struct Margin + { + Margin() noexcept; + Margin (float size) noexcept; + + float left; + float right; + float top; + float bottom; + }; + + /** */ + Margin margin; + + /** */ + juce::Rectangle currentBounds; + + /** Short-hand */ + void setArea (Property rowStart, Property columnStart, Property rowEnd, Property columnEnd); + + /** Short-hand, span of 1 by default */ + void setArea (Property rowStart, Property columnStart); + + /** Short-hand */ + void setArea (const juce::String& areaName); + + /** Short-hand */ + GridItem withArea (Property rowStart, Property columnStart, Property rowEnd, Property columnEnd) const noexcept; + + /** Short-hand, span of 1 by default */ + GridItem withArea (Property rowStart, Property columnStart) const noexcept; + + /** Short-hand */ + GridItem withArea (const juce::String& areaName) const noexcept; + + /** */ + GridItem withRow (StartAndEndProperty row) const noexcept; + + /** */ + GridItem withColumn (StartAndEndProperty column) const noexcept; + + /** */ + GridItem withAlignSelf (AlignSelf newAlignSelf) const noexcept; + + /** */ + GridItem withJustifySelf (JustifySelf newJustifySelf) const noexcept; + + /** */ + GridItem withWidth (float newWidth) const noexcept; + + /** */ + GridItem withHeight (float newHeight) const noexcept; + + /** */ + GridItem withSize (float newWidth, float newHeight) const noexcept; + + /** */ + GridItem withMargin (Margin newMargin) const noexcept; + + /** Returns a copy of this object with a new order. */ + GridItem withOrder (int newOrder) const noexcept; +}; diff --git a/modules/juce_gui_basics/layout/juce_GridUnitTests.cpp b/modules/juce_gui_basics/layout/juce_GridUnitTests.cpp new file mode 100644 index 0000000000..52ccda6bd9 --- /dev/null +++ b/modules/juce_gui_basics/layout/juce_GridUnitTests.cpp @@ -0,0 +1,257 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2017 - ROLI Ltd. + + JUCE is an open source library subject to commercial or open-source + licensing. + + By using JUCE, you agree to the terms of both the JUCE 5 End-User License + Agreement and JUCE 5 Privacy Policy (both updated and effective as of the + 27th April 2017). + + End User License Agreement: www.juce.com/juce-5-licence + Privacy Policy: www.juce.com/juce-5-privacy-policy + + Or: You may also use this code under the terms of the GPL v3 (see + www.gnu.org/licenses). + + JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER + EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE + DISCLAIMED. + + ============================================================================== +*/ + +struct GridTests : public juce::UnitTest +{ + GridTests() : juce::UnitTest ("Grid class") {} + + void runTest() override + { + using Fr = juce::Grid::Fr; + using Tr = juce::Grid::TrackInfo; + using Rect = juce::Rectangle; + using Grid = juce::Grid; + + { + Grid grid; + + grid.templateColumns.add (Tr (1_fr)); + grid.templateRows.addArray ({ Tr (20_px), Tr (1_fr) }); + + grid.items.addArray ({ GridItem().withArea (1, 1), + GridItem().withArea (2, 1) }); + + grid.performLayout (juce::Rectangle (200, 400)); + + beginTest ("Layout calculation test: 1 column x 2 rows: no gap"); + expect (grid.items[0].currentBounds == Rect (0.0f, 0.0f, 200.f, 20.0f)); + expect (grid.items[1].currentBounds == Rect (0.0f, 20.0f, 200.f, 380.0f)); + + grid.templateColumns.add (Tr (50_px)); + grid.templateRows.add (Tr (2_fr)); + + grid.items.addArray ( { GridItem().withArea (1, 2), + GridItem().withArea (2, 2), + GridItem().withArea (3, 1), + GridItem().withArea (3, 2) }); + + grid.performLayout (juce::Rectangle (150, 170)); + + beginTest ("Layout calculation test: 2 columns x 3 rows: no gap"); + expect (grid.items[0].currentBounds == Rect (0.0f, 0.0f, 100.0f, 20.0f)); + expect (grid.items[1].currentBounds == Rect (0.0f, 20.0f, 100.0f, 50.0f)); + expect (grid.items[2].currentBounds == Rect (100.0f, 0.0f, 50.0f, 20.0f)); + expect (grid.items[3].currentBounds == Rect (100.0f, 20.0f, 50.0f, 50.0f)); + expect (grid.items[4].currentBounds == Rect (0.0f, 70.0f, 100.0f, 100.0f)); + expect (grid.items[5].currentBounds == Rect (100.0f, 70.0f, 50.0f, 100.0f)); + + grid.columnGap = 20_px; + grid.rowGap = 10_px; + + grid.performLayout (juce::Rectangle (200, 310)); + + beginTest ("Layout calculation test: 2 columns x 3 rows: rowGap of 10 and columnGap of 20"); + expect (grid.items[0].currentBounds == Rect (0.0f, 0.0f, 130.0f, 20.0f)); + expect (grid.items[1].currentBounds == Rect (0.0f, 30.0f, 130.0f, 90.0f)); + expect (grid.items[2].currentBounds == Rect (150.0f, 0.0f, 50.0f, 20.0f)); + expect (grid.items[3].currentBounds == Rect (150.0f, 30.0f, 50.0f, 90.0f)); + expect (grid.items[4].currentBounds == Rect (0.0f, 130.0f, 130.0f, 180.0f)); + expect (grid.items[5].currentBounds == Rect (150.0f, 130.0f, 50.0f, 180.0f)); + } + + { + Grid grid; + + grid.templateColumns.addArray ({ Tr ("first", 20_px, "in"), Tr ("in", 1_fr, "in"), Tr (20_px, "last") }); + grid.templateRows.addArray ({ Tr (1_fr), + Tr (20_px)}); + + { + beginTest ("Grid items placement tests: integer and custom ident, counting forward"); + + GridItem i1, i2, i3, i4, i5; + i1.column = { 1, 4 }; + i1.row = { 1, 2 }; + + i2.column = { 1, 3 }; + i2.row = { 1, 3 }; + + i3.column = { "first", "in" }; + i3.row = { 2, 3 }; + + i4.column = { "first", { 2, "in" } }; + i4.row = { 1, 2 }; + + i5.column = { "first", "last" }; + i5.row = { 1, 2 }; + + grid.items.addArray ({ i1, i2, i3, i4, i5 }); + + grid.performLayout ({ 140, 100 }); + + expect (grid.items[0].currentBounds == Rect (0.0f, 0.0f, 140.0f, 80.0f)); + expect (grid.items[1].currentBounds == Rect (0.0f, 0.0f, 120.0f, 100.0f)); + expect (grid.items[2].currentBounds == Rect (0.0f, 80.0f, 20.0f, 20.0f)); + expect (grid.items[3].currentBounds == Rect (0.0f, 0.0f, 120.0f, 80.0f)); + expect (grid.items[4].currentBounds == Rect (0.0f, 0.0f, 140.0f, 80.0f)); + } + } + + { + Grid grid; + + grid.templateColumns.addArray ({ Tr ("first", 20_px, "in"), Tr ("in", 1_fr, "in"), Tr (20_px, "last") }); + grid.templateRows.addArray ({ Tr (1_fr), + Tr (20_px)}); + + beginTest ("Grid items placement tests: integer and custom ident, counting forward, reversed end and start"); + + GridItem i1, i2, i3, i4, i5; + i1.column = { 4, 1 }; + i1.row = { 2, 1 }; + + i2.column = { 3, 1 }; + i2.row = { 3, 1 }; + + i3.column = { "in", "first" }; + i3.row = { 3, 2 }; + + i4.column = { "first", { 2, "in" } }; + i4.row = { 1, 2 }; + + i5.column = { "last", "first" }; + i5.row = { 1, 2 }; + + grid.items.addArray ({ i1, i2, i3, i4, i5 }); + + grid.performLayout ({ 140, 100 }); + + expect (grid.items[0].currentBounds == Rect (0.0f, 0.0f, 140.0f, 80.0f)); + expect (grid.items[1].currentBounds == Rect (0.0f, 0.0f, 120.0f, 100.0f)); + expect (grid.items[2].currentBounds == Rect (0.0f, 80.0f, 20.0f, 20.0f)); + expect (grid.items[3].currentBounds == Rect (0.0f, 0.0f, 120.0f, 80.0f)); + expect (grid.items[4].currentBounds == Rect (0.0f, 0.0f, 140.0f, 80.0f)); + } + + { + beginTest ("Grid items placement tests: areas"); + + Grid grid; + + grid.templateColumns = { Tr (50_px), Tr (100_px), Tr (Fr (1_fr)), Tr (50_px) }; + grid.templateRows = { Tr (50_px), + Tr (1_fr), + Tr (50_px) }; + + grid.templateAreas = { "header header header header", + "main main . sidebar", + "footer footer footer footer" }; + + grid.items.addArray ({ GridItem().withArea ("header"), + GridItem().withArea ("main"), + GridItem().withArea ("sidebar"), + GridItem().withArea ("footer"), + }); + + grid.performLayout ({ 300, 150 }); + + expect (grid.items[0].currentBounds == Rect (0.f, 0.f, 300.f, 50.f)); + expect (grid.items[1].currentBounds == Rect (0.f, 50.f, 150.f, 50.f)); + expect (grid.items[2].currentBounds == Rect (250.f, 50.f, 50.f, 50.f)); + expect (grid.items[3].currentBounds == Rect (0.f, 100.f, 300.f, 50.f)); + } + + { + beginTest ("Grid implicit rows and columns: triggered by areas"); + + Grid grid; + + grid.templateColumns = { Tr (50_px), Tr (100_px), Tr (1_fr), Tr (50_px) }; + grid.templateRows = { Tr (50_px), + Tr (1_fr), + Tr (50_px) }; + + grid.autoRows = Tr (30_px); + grid.autoColumns = Tr (30_px); + + grid.templateAreas = { "header header header header header", + "main main . sidebar sidebar", + "footer footer footer footer footer", + "sub sub sub sub sub"}; + + grid.items.addArray ({ GridItem().withArea ("header"), + GridItem().withArea ("main"), + GridItem().withArea ("sidebar"), + GridItem().withArea ("footer"), + GridItem().withArea ("sub"), + }); + + grid.performLayout ({ 330, 180 }); + + expect (grid.items[0].currentBounds == Rect (0.f, 0.f, 330.f, 50.f)); + expect (grid.items[1].currentBounds == Rect (0.f, 50.f, 150.f, 50.f)); + expect (grid.items[2].currentBounds == Rect (250.f, 50.f, 80.f, 50.f)); + expect (grid.items[3].currentBounds == Rect (0.f, 100.f, 330.f, 50.f)); + expect (grid.items[4].currentBounds == Rect (0.f, 150.f, 330.f, 30.f)); + } + + { + beginTest ("Grid implicit rows and columns: triggered by areas"); + + Grid grid; + + grid.templateColumns = { Tr (50_px), Tr (100_px), Tr (1_fr), Tr (50_px) }; + grid.templateRows = { Tr (50_px), + Tr (1_fr), + Tr (50_px) }; + + grid.autoRows = Tr (1_fr); + grid.autoColumns = Tr (1_fr); + + grid.templateAreas = { "header header header header", + "main main . sidebar", + "footer footer footer footer" }; + + grid.items.addArray ({ GridItem().withArea ("header"), + GridItem().withArea ("main"), + GridItem().withArea ("sidebar"), + GridItem().withArea ("footer"), + GridItem().withArea (4, 5, 6, 7) + }); + + grid.performLayout ({ 350, 250 }); + + expect (grid.items[0].currentBounds == Rect (0.f, 0.f, 250.f, 50.f)); + expect (grid.items[1].currentBounds == Rect (0.f, 50.f, 150.f, 50.f)); + expect (grid.items[2].currentBounds == Rect (200.f, 50.f, 50.f, 50.f)); + expect (grid.items[3].currentBounds == Rect (0.f, 100.f, 250.f, 50.f)); + expect (grid.items[4].currentBounds == Rect (250.f, 150.f, 100.f, 100.f)); + } + + } +}; + +static GridTests gridUnitTests;