diff --git a/gdb/ChangeLog b/gdb/ChangeLog
index 37202a9..d462479 100644
--- a/gdb/ChangeLog
+++ b/gdb/ChangeLog
@@ -1,5 +1,21 @@
 2019-11-12  Tom Tromey  <tom@tromey.com>
 
+	* tui/tui-layout.h (class tui_layout_base)
+	(class tui_layout_window, class tui_layout_split): New.
+	* tui/tui-layout.c (tui_get_window_by_name)
+	(tui_layout_window::clone, tui_layout_window::apply)
+	(tui_layout_window::get_sizes, tui_layout_window::add_split)
+	(tui_layout_split::add_window, tui_layout_split::clone)
+	(tui_layout_split::get_sizes)
+	(tui_layout_split::set_weights_from_heights)
+	(tui_layout_split::adjust_size, tui_layout_split::apply): New
+	functions.
+	(tui_layout_split::add_split, tui_layout_split::add_split)
+	(tui_layout_split::set_weights_from_heights)
+	(tui_layout_split::set_weights_from_heights): New functions.
+
+2019-11-12  Tom Tromey  <tom@tromey.com>
+
 	* tui/tui-wingeneral.c (tui_gen_win_info::make_window): Update.
 	* tui/tui-win.c (tui_adjust_win_heights, tui_resize_all): Update.
 	* tui/tui-layout.c (tui_gen_win_info::resize): Update.
diff --git a/gdb/tui/tui-layout.c b/gdb/tui/tui-layout.c
index 0a7812a..91f270d 100644
--- a/gdb/tui/tui-layout.c
+++ b/gdb/tui/tui-layout.c
@@ -543,6 +543,366 @@
 
 
 
+/* Helper function that returns a TUI window, given its name.  */
+
+static tui_gen_win_info *
+tui_get_window_by_name (const std::string &name)
+{
+  if (name == "src")
+    {
+      if (tui_win_list[SRC_WIN] == nullptr)
+	tui_win_list[SRC_WIN] = new tui_source_window ();
+      return tui_win_list[SRC_WIN];
+    }
+  else if (name == "cmd")
+    {
+      if (tui_win_list[CMD_WIN] == nullptr)
+	tui_win_list[CMD_WIN] = new tui_cmd_window ();
+      return tui_win_list[CMD_WIN];
+    }
+  else if (name == "regs")
+    {
+      if (tui_win_list[DATA_WIN] == nullptr)
+	tui_win_list[DATA_WIN] = new tui_data_window ();
+      return tui_win_list[DATA_WIN];
+    }
+  else if (name == "asm")
+    {
+      if (tui_win_list[DISASSEM_WIN] == nullptr)
+	tui_win_list[DISASSEM_WIN] = new tui_disasm_window ();
+      return tui_win_list[DISASSEM_WIN];
+    }
+  else
+    {
+      gdb_assert (name == "locator");
+      return tui_locator_win_info_ptr ();
+    }
+}
+
+/* See tui-layout.h.  */
+
+std::unique_ptr<tui_layout_base>
+tui_layout_window::clone () const
+{
+  tui_layout_window *result = new tui_layout_window (m_contents.c_str ());
+  return std::unique_ptr<tui_layout_base> (result);
+}
+
+/* See tui-layout.h.  */
+
+void
+tui_layout_window::apply (int x_, int y_, int width_, int height_)
+{
+  x = x_;
+  y = y_;
+  width = width_;
+  height = height_;
+  gdb_assert (m_window != nullptr);
+  m_window->resize (height, width, x, y);
+}
+
+/* See tui-layout.h.  */
+
+void
+tui_layout_window::get_sizes (int *min_height, int *max_height)
+{
+  if (m_window == nullptr)
+    m_window = tui_get_window_by_name (m_contents);
+  *min_height = m_window->min_height ();
+  *max_height = m_window->max_height ();
+}
+
+/* See tui-layout.h.  */
+
+bool
+tui_layout_window::top_boxed_p () const
+{
+  gdb_assert (m_window != nullptr);
+  return m_window->can_box ();
+}
+
+/* See tui-layout.h.  */
+
+bool
+tui_layout_window::bottom_boxed_p () const
+{
+  gdb_assert (m_window != nullptr);
+  return m_window->can_box ();
+}
+
+/* See tui-layout.h.  */
+
+tui_layout_split *
+tui_layout_split::add_split (int weight)
+{
+  tui_layout_split *result = new tui_layout_split ();
+  split s = {weight, std::unique_ptr<tui_layout_base> (result)};
+  m_splits.push_back (std::move (s));
+  return result;
+}
+
+/* See tui-layout.h.  */
+
+void
+tui_layout_split::add_window (const char *name, int weight)
+{
+  tui_layout_window *result = new tui_layout_window (name);
+  split s = {weight, std::unique_ptr<tui_layout_base> (result)};
+  m_splits.push_back (std::move (s));
+}
+
+/* See tui-layout.h.  */
+
+std::unique_ptr<tui_layout_base>
+tui_layout_split::clone () const
+{
+  tui_layout_split *result = new tui_layout_split ();
+  for (const split &item : m_splits)
+    {
+      std::unique_ptr<tui_layout_base> next = item.layout->clone ();
+      split s = {item.weight, std::move (next)};
+      result->m_splits.push_back (std::move (s));
+    }
+  return std::unique_ptr<tui_layout_base> (result);
+}
+
+/* See tui-layout.h.  */
+
+void
+tui_layout_split::get_sizes (int *min_height, int *max_height)
+{
+  *min_height = 0;
+  *max_height = 0;
+  for (const split &item : m_splits)
+    {
+      int new_min, new_max;
+      item.layout->get_sizes (&new_min, &new_max);
+      *min_height += new_min;
+      *max_height += new_max;
+    }
+}
+
+/* See tui-layout.h.  */
+
+bool
+tui_layout_split::top_boxed_p () const
+{
+  if (m_splits.empty ())
+    return false;
+  return m_splits[0].layout->top_boxed_p ();
+}
+
+/* See tui-layout.h.  */
+
+bool
+tui_layout_split::bottom_boxed_p () const
+{
+  if (m_splits.empty ())
+    return false;
+  return m_splits.back ().layout->top_boxed_p ();
+}
+
+/* See tui-layout.h.  */
+
+void
+tui_layout_split::set_weights_from_heights ()
+{
+  for (int i = 0; i < m_splits.size (); ++i)
+    m_splits[i].weight = m_splits[i].layout->height;
+}
+
+/* See tui-layout.h.  */
+
+bool
+tui_layout_split::adjust_size (const char *name, int new_height)
+{
+  /* Look through the children.  If one is a layout holding the named
+     window, we're done; or if one actually is the named window,
+     update it.  */
+  int found_index = -1;
+  for (int i = 0; i < m_splits.size (); ++i)
+    {
+      if (m_splits[i].layout->adjust_size (name, new_height))
+	return true;
+      const char *win_name = m_splits[i].layout->get_name ();
+      if (win_name != nullptr && strcmp (name, win_name) == 0)
+	{
+	  found_index = i;
+	  break;
+	}
+    }
+
+  if (found_index == -1)
+    return false;
+  if (m_splits[found_index].layout->height == new_height)
+    return true;
+
+  set_weights_from_heights ();
+  int delta = m_splits[found_index].weight - new_height;
+  m_splits[found_index].weight = new_height;
+
+  /* Distribute the "delta" over the next window; but if the next
+     window cannot hold it all, keep going until we either find a
+     window that does, or until we loop all the way around.  */
+  for (int i = 0; delta != 0 && i < m_splits.size () - 1; ++i)
+    {
+      int index = (found_index + 1 + i) % m_splits.size ();
+
+      int new_min, new_max;
+      m_splits[index].layout->get_sizes (&new_min, &new_max);
+
+      if (delta < 0)
+	{
+	  /* The primary window grew, so we are trying to shrink other
+	     windows.  */
+	  int available = m_splits[index].weight - new_min;
+	  int shrink_by = std::min (available, -delta);
+	  m_splits[index].weight -= shrink_by;
+	  delta += shrink_by;
+	}
+      else
+	{
+	  /* The primary window shrank, so we are trying to grow other
+	     windows.  */
+	  int available = new_max - m_splits[index].weight;
+	  int grow_by = std::min (available, delta);
+	  m_splits[index].weight += grow_by;
+	  delta -= grow_by;
+	}
+    }
+
+  if (delta != 0)
+    {
+      warning (_("Invalid window height specified"));
+      /* Effectively undo any modifications made here.  */
+      set_weights_from_heights ();
+    }
+  else
+    {
+      /* Simply re-apply the updated layout.  */
+      apply (x, y, width, height);
+    }
+
+  return true;
+}
+
+/* See tui-layout.h.  */
+
+void
+tui_layout_split::apply (int x_, int y_, int width_, int height_)
+{
+  x = x_;
+  y = y_;
+  width = width_;
+  height = height_;
+
+  struct height_info
+  {
+    int height;
+    int min_height;
+    int max_height;
+    /* True if this window will share a box border with the previous
+       window in the list.  */
+    bool share_box;
+  };
+
+  std::vector<height_info> info (m_splits.size ());
+
+  /* Step 1: Find the min and max height of each sub-layout.
+     Fixed-sized layouts are given their desired height, and then the
+     remaining space is distributed among the remaining windows
+     according to the weights given.  */
+  int available_height = height;
+  int last_index = -1;
+  int total_weight = 0;
+  for (int i = 0; i < m_splits.size (); ++i)
+    {
+      bool cmd_win_already_exists = TUI_CMD_WIN != nullptr;
+
+      /* Always call get_sizes, to ensure that the window is
+	 instantiated.  This is a bit gross but less gross than adding
+	 special cases for this in other places.  */
+      m_splits[i].layout->get_sizes (&info[i].min_height, &info[i].max_height);
+
+      if (!m_applied
+	  && cmd_win_already_exists
+	  && m_splits[i].layout->get_name () != nullptr
+	  && strcmp (m_splits[i].layout->get_name (), "cmd") == 0)
+	{
+	  /* If this layout has never been applied, then it means the
+	     user just changed the layout.  In this situation, it's
+	     desirable to keep the size of the command window the
+	     same.  Setting the min and max heights this way ensures
+	     that the resizing step, below, does the right thing with
+	     this window.  */
+	  info[i].min_height = TUI_CMD_WIN->height;
+	  info[i].max_height = TUI_CMD_WIN->height;
+	}
+
+      if (info[i].min_height == info[i].max_height)
+	available_height -= info[i].min_height;
+      else
+	{
+	  last_index = i;
+	  total_weight += m_splits[i].weight;
+	}
+
+      /* Two adjacent boxed windows will share a border, making a bit
+	 more height available.  */
+      if (i > 0
+	  && m_splits[i - 1].layout->bottom_boxed_p ()
+	  && m_splits[i].layout->top_boxed_p ())
+	info[i].share_box = true;
+    }
+
+  /* Step 2: Compute the height of each sub-layout.  Fixed-sized items
+     are given their fixed size, while others are resized according to
+     their weight.  */
+  int used_height = 0;
+  for (int i = 0; i < m_splits.size (); ++i)
+    {
+      /* Compute the height and clamp to the allowable range.  */
+      info[i].height = available_height * m_splits[i].weight / total_weight;
+      if (info[i].height > info[i].max_height)
+	info[i].height = info[i].max_height;
+      if (info[i].height < info[i].min_height)
+	info[i].height = info[i].min_height;
+      /* If there is any leftover height, just redistribute it to the
+	 last resizeable window, by dropping it from the allocated
+	 height.  We could try to be fancier here perhaps, by
+	 redistributing this height among all windows, not just the
+	 last window.  */
+      if (info[i].min_height != info[i].max_height)
+	{
+	  used_height += info[i].height;
+	  if (info[i].share_box)
+	    --used_height;
+	}
+    }
+
+  /* Allocate any leftover height.  */
+  if (available_height >= used_height && last_index != -1)
+    info[last_index].height += available_height - used_height;
+
+  /* Step 3: Resize.  */
+  int height_accum = 0;
+  for (int i = 0; i < m_splits.size (); ++i)
+    {
+      /* If we fall off the bottom, just make allocations overlap.
+	 GIGO.  */
+      if (height_accum + info[i].height > height)
+	height_accum = height - info[i].height;
+      else if (info[i].share_box)
+	--height_accum;
+      m_splits[i].layout->apply (x, y + height_accum, width, info[i].height);
+      height_accum += info[i].height;
+    }
+
+  m_applied = true;
+}
+
+
+
 /* Function to initialize gdb commands, for tui window layout
    manipulation.  */
 
diff --git a/gdb/tui/tui-layout.h b/gdb/tui/tui-layout.h
index 23f05f3..d7f0731 100644
--- a/gdb/tui/tui-layout.h
+++ b/gdb/tui/tui-layout.h
@@ -25,6 +25,152 @@
 #include "tui/tui.h"
 #include "tui/tui-data.h"
 
+/* The basic object in a TUI layout.  This represents a single piece
+   of screen real estate.  Subclasses determine the exact
+   behavior.  */
+class tui_layout_base
+{
+public:
+
+  DISABLE_COPY_AND_ASSIGN (tui_layout_base);
+
+  /* Clone this object.  Ordinarily a layout is cloned before it is
+     used, so that any necessary modifications do not affect the
+     "skeleton" layout.  */
+  virtual std::unique_ptr<tui_layout_base> clone () const = 0;
+
+  /* Change the size and location of this layout.  */
+  virtual void apply (int x, int y, int width, int height) = 0;
+
+  /* Return the minimum and maximum height of this layout.  */
+  virtual void get_sizes (int *min_height, int *max_height) = 0;
+
+  /* True if the topmost item in this layout is boxed.  */
+  virtual bool top_boxed_p () const = 0;
+
+  /* True if the bottommost item in this layout is boxed.  */
+  virtual bool bottom_boxed_p () const = 0;
+
+  /* Return the name of this layout's window, or nullptr if this
+     layout does not represent a single window.  */
+  virtual const char *get_name () const
+  {
+    return nullptr;
+  }
+
+  /* Adjust the size of the window named NAME to NEW_HEIGHT, updating
+     the sizes of the other windows around it.  */
+  virtual bool adjust_size (const char *name, int new_height) = 0;
+
+  /* The most recent space allocation.  */
+  int x = 0;
+  int y = 0;
+  int width = 0;
+  int height = 0;
+
+protected:
+
+  tui_layout_base () = default;
+};
+
+/* A TUI layout object that displays a single window.  The window is
+   given by name.  */
+class tui_layout_window : public tui_layout_base
+{
+public:
+
+  explicit tui_layout_window (const char *name)
+    : m_contents (name)
+  {
+  }
+
+  DISABLE_COPY_AND_ASSIGN (tui_layout_window);
+
+  std::unique_ptr<tui_layout_base> clone () const override;
+
+  void apply (int x, int y, int width, int height) override;
+
+  const char *get_name () const override
+  {
+    return m_contents.c_str ();
+  }
+
+  bool adjust_size (const char *name, int new_height) override
+  {
+    return false;
+  }
+
+  bool top_boxed_p () const override;
+
+  bool bottom_boxed_p () const override;
+
+protected:
+
+  void get_sizes (int *min_height, int *max_height) override;
+
+private:
+
+  /* Type of content to display.  */
+  std::string m_contents;
+
+  /* When a layout is applied, this is updated to point to the window
+     object.  */
+  tui_gen_win_info *m_window = nullptr;
+};
+
+/* A TUI layout that holds other layouts.  */
+class tui_layout_split : public tui_layout_base
+{
+public:
+
+  tui_layout_split () = default;
+
+  DISABLE_COPY_AND_ASSIGN (tui_layout_split);
+
+  /* Add a new split layout to this layout.  WEIGHT is the desired
+     size, which is relative to the other weights given in this
+     layout.  */
+  tui_layout_split *add_split (int weight);
+
+  /* Add a new window to this layout.  NAME is the name of the window
+     to add.  WEIGHT is the desired size, which is relative to the
+     other weights given in this layout.  */
+  void add_window (const char *name, int weight);
+
+  std::unique_ptr<tui_layout_base> clone () const override;
+
+  void apply (int x, int y, int width, int height) override;
+
+  bool adjust_size (const char *name, int new_height) override;
+
+  bool top_boxed_p () const override;
+
+  bool bottom_boxed_p () const override;
+
+protected:
+
+  void get_sizes (int *min_height, int *max_height) override;
+
+private:
+
+  /* Set the weights from the current heights.  */
+  void set_weights_from_heights ();
+
+  struct split
+  {
+    /* The requested weight.  */
+    int weight;
+    /* The layout.  */
+    std::unique_ptr<tui_layout_base> layout;
+  };
+
+  /* The splits.  */
+  std::vector<split> m_splits;
+
+  /* True if this layout has already been applied at least once.  */
+  bool m_applied = false;
+};
+
 extern void tui_add_win_to_layout (enum tui_win_type);
 extern void tui_set_layout (enum tui_layout_type);
 
