add a title to the logging console (#2066)

add a title to the logging console
diff --git a/packages/devtools_app/lib/src/common_widgets.dart b/packages/devtools_app/lib/src/common_widgets.dart
index 757d8ce..78cf724 100644
--- a/packages/devtools_app/lib/src/common_widgets.dart
+++ b/packages/devtools_app/lib/src/common_widgets.dart
@@ -22,6 +22,8 @@
 
 const defaultDialogRadius = 20.0;
 
+const areaPaneHeaderHeight = 36.0;
+
 List<Widget> headerInColumn(TextTheme textTheme, String title) {
   return [
     Text(title, style: textTheme.headline6),
@@ -418,6 +420,84 @@
   }
 }
 
+/// A wrapper around a FlatButton, an Icon, and an optional Tooltip; used for
+/// small toolbar actions.
+class ToolbarAction extends StatelessWidget {
+  const ToolbarAction({
+    @required this.icon,
+    @required this.onPressed,
+    this.tooltip,
+    Key key,
+  }) : super(key: key);
+
+  final IconData icon;
+  final String tooltip;
+  final VoidCallback onPressed;
+
+  @override
+  Widget build(BuildContext context) {
+    final button = FlatButton(
+      padding: EdgeInsets.zero,
+      materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
+      onPressed: onPressed,
+      child: Icon(icon, size: actionsIconSize),
+    );
+
+    return tooltip == null
+        ? button
+        : Tooltip(
+            message: tooltip,
+            waitDuration: tooltipWait,
+            child: button,
+          );
+  }
+}
+
+/// Create a bordered, fixed-height header area with a title and optional child
+/// on the right-hand side.
+///
+/// This is typically used as a title for a logical area of the screen.
+// TODO(devoncarew): Refactor this into an 'AreaPaneHeader' widget.
+SizedBox areaPaneHeader(
+  BuildContext context, {
+  @required String title,
+  bool needsTopBorder = true,
+  List<Widget> actions = const [],
+  double rightPadding = densePadding,
+}) {
+  final theme = Theme.of(context);
+
+  return SizedBox(
+    height: areaPaneHeaderHeight,
+    child: Container(
+      decoration: BoxDecoration(
+        border: Border(
+          top: needsTopBorder
+              ? BorderSide(color: theme.focusColor)
+              : BorderSide.none,
+          bottom: BorderSide(color: theme.focusColor),
+        ),
+        color: titleSolidBackgroundColor(theme),
+      ),
+      padding: EdgeInsets.only(left: defaultSpacing, right: rightPadding),
+      alignment: Alignment.centerLeft,
+      child: Row(
+        children: [
+          Expanded(
+            child: Text(
+              title,
+              maxLines: 1,
+              overflow: TextOverflow.ellipsis,
+              style: Theme.of(context).textTheme.subtitle2,
+            ),
+          ),
+          ...actions,
+        ],
+      ),
+    ),
+  );
+}
+
 /// A FlatButton used to close a containing dialog.
 class DialogCloseButton extends StatelessWidget {
   @override
diff --git a/packages/devtools_app/lib/src/console.dart b/packages/devtools_app/lib/src/console.dart
index fe0ec2e..2941bb3 100644
--- a/packages/devtools_app/lib/src/console.dart
+++ b/packages/devtools_app/lib/src/console.dart
@@ -135,38 +135,6 @@
 
 // CONTROLS
 
-/// A pre-configured IconButton that fits the ux of the Console widget.
-///
-/// The customizations are:
-///  * Icon size: [actionsIconSize]
-///  * Do not show [tooltip] if the button is disabled
-///  * [VisualDensity.compact]
-class ConsoleControl extends StatelessWidget {
-  const ConsoleControl({
-    this.icon,
-    this.tooltip,
-    this.onPressed,
-    this.buttonKey,
-  });
-
-  final IconData icon;
-  final String tooltip;
-  final VoidCallback onPressed;
-  final Key buttonKey;
-
-  @override
-  Widget build(BuildContext context) {
-    final disabled = onPressed == null;
-    return IconButton(
-      icon: Icon(icon, size: actionsIconSize),
-      onPressed: onPressed,
-      tooltip: disabled ? null : tooltip,
-      visualDensity: VisualDensity.compact,
-      key: buttonKey,
-    );
-  }
-}
-
 /// A Console Control to "delete" the contents of the console.
 ///
 /// This just preconfigures a ConsoleControl with the `delete` icon,
@@ -184,11 +152,11 @@
 
   @override
   Widget build(BuildContext context) {
-    return ConsoleControl(
+    return ToolbarAction(
       icon: Icons.delete,
       tooltip: tooltip,
       onPressed: onPressed,
-      buttonKey: buttonKey,
+      key: buttonKey,
     );
   }
 }
@@ -215,13 +183,13 @@
   @override
   Widget build(BuildContext context) {
     final disabled = dataProvider == null;
-    return ConsoleControl(
+    return ToolbarAction(
       icon: Icons.content_copy,
       tooltip: tooltip,
       onPressed: disabled
           ? null
           : () => copyToClipboard(dataProvider(), successMessage, context),
-      buttonKey: buttonKey,
+      key: buttonKey,
     );
   }
 }
diff --git a/packages/devtools_app/lib/src/debugger/codeview.dart b/packages/devtools_app/lib/src/debugger/codeview.dart
index 4c6854a..dd54894 100644
--- a/packages/devtools_app/lib/src/debugger/codeview.dart
+++ b/packages/devtools_app/lib/src/debugger/codeview.dart
@@ -284,18 +284,18 @@
           theme,
           child: Row(
             children: [
-              DebuggerToolbarAction(
-                Icons.chevron_left,
+              ToolbarAction(
+                icon: Icons.chevron_left,
                 onPressed:
                     scriptsHistory.hasPrevious ? scriptsHistory.moveBack : null,
               ),
-              DebuggerToolbarAction(
-                Icons.chevron_right,
+              ToolbarAction(
+                icon: Icons.chevron_right,
                 onPressed:
                     scriptsHistory.hasNext ? scriptsHistory.moveForward : null,
               ),
               const SizedBox(width: denseSpacing),
-              const VerticalDivider(),
+              const VerticalDivider(thickness: 1.0),
               const SizedBox(width: defaultSpacing),
               Expanded(
                 child: Text(
@@ -304,7 +304,6 @@
                 ),
               ),
               const SizedBox(width: denseSpacing),
-              const VerticalDivider(),
               PopupMenuButton<ScriptRef>(
                 itemBuilder: _buildScriptMenuFromHistory,
                 enabled: scriptsHistory.hasScripts,
diff --git a/packages/devtools_app/lib/src/debugger/common.dart b/packages/devtools_app/lib/src/debugger/common.dart
index 74c5394..561344d 100644
--- a/packages/devtools_app/lib/src/debugger/common.dart
+++ b/packages/devtools_app/lib/src/debugger/common.dart
@@ -4,8 +4,8 @@
 
 import 'package:flutter/material.dart';
 
+import '../common_widgets.dart';
 import '../theme.dart';
-import 'debugger_screen.dart';
 
 /// Create a header area for a debugger component.
 ///
@@ -23,7 +23,7 @@
     ),
     padding: const EdgeInsets.only(left: defaultSpacing),
     alignment: Alignment.centerLeft,
-    height: DebuggerScreen.debuggerPaneHeaderHeight,
+    height: areaPaneHeaderHeight,
     child: child != null ? child : Text(text, style: theme.textTheme.subtitle2),
   );
 }
@@ -45,24 +45,3 @@
     decoration: BoxDecoration(color: color, shape: BoxShape.circle),
   );
 }
-
-/// A wrapper around a FlatButton and an Icon, used for small toolbar actions.
-class DebuggerToolbarAction extends StatelessWidget {
-  const DebuggerToolbarAction(
-    this.icon, {
-    @required this.onPressed,
-  });
-
-  final IconData icon;
-  final VoidCallback onPressed;
-
-  @override
-  Widget build(BuildContext context) {
-    return FlatButton(
-      padding: EdgeInsets.zero,
-      materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
-      onPressed: onPressed,
-      child: Icon(icon, size: actionsIconSize),
-    );
-  }
-}
diff --git a/packages/devtools_app/lib/src/debugger/console.dart b/packages/devtools_app/lib/src/debugger/console.dart
index 45b634a..231f205 100644
--- a/packages/devtools_app/lib/src/debugger/console.dart
+++ b/packages/devtools_app/lib/src/debugger/console.dart
@@ -8,7 +8,6 @@
 import '../common_widgets.dart';
 import '../console.dart';
 import '../utils.dart';
-import 'common.dart';
 import 'debugger_controller.dart';
 
 // TODO(devoncarew): Show some small UI indicator when we receive stdout/stderr.
@@ -60,20 +59,25 @@
 
     return OutlineDecoration(
       child: Console(
-        title: debuggerSectionTitle(Theme.of(context), text: 'Console'),
+        title: areaPaneHeader(
+          context,
+          title: 'Console',
+          needsTopBorder: false,
+          actions: [
+            CopyToClipboardControl(
+              dataProvider: disabled ? null : () => _lines.join('\n'),
+              successMessage:
+                  'Copied $numLines ${pluralize('line', numLines)}.',
+              buttonKey: DebuggerConsole.copyToClipboardButtonKey,
+            ),
+            DeleteControl(
+              buttonKey: DebuggerConsole.clearStdioButtonKey,
+              tooltip: 'Clear console output',
+              onPressed: disabled ? null : widget.controller.clearStdio,
+            ),
+          ],
+        ),
         lines: _lines,
-        controls: [
-          CopyToClipboardControl(
-            dataProvider: disabled ? null : () => _lines.join('\n'),
-            successMessage: 'Copied $numLines ${pluralize('line', numLines)}.',
-            buttonKey: DebuggerConsole.copyToClipboardButtonKey,
-          ),
-          DeleteControl(
-            onPressed: disabled ? null : widget.controller.clearStdio,
-            tooltip: 'Clear console output',
-            buttonKey: DebuggerConsole.clearStdioButtonKey,
-          ),
-        ],
       ),
     );
   }
diff --git a/packages/devtools_app/lib/src/debugger/debugger_screen.dart b/packages/devtools_app/lib/src/debugger/debugger_screen.dart
index e156a09..a5ab625 100644
--- a/packages/devtools_app/lib/src/debugger/debugger_screen.dart
+++ b/packages/devtools_app/lib/src/debugger/debugger_screen.dart
@@ -21,7 +21,6 @@
 import 'breakpoints.dart';
 import 'call_stack.dart';
 import 'codeview.dart';
-import 'common.dart';
 import 'console.dart';
 import 'controls.dart';
 import 'debugger_controller.dart';
@@ -37,8 +36,6 @@
   const DebuggerScreen()
       : super('debugger', title: 'Debugger', icon: Octicons.bug);
 
-  static const debuggerPaneHeaderHeight = 36.0;
-
   @override
   String get docPageId => screenId;
 
@@ -211,20 +208,20 @@
           totalHeight: constraints.maxHeight,
           initialFractions: const [0.38, 0.38, 0.24],
           minSizes: const [0.0, 0.0, 0.0],
-          headers: [
-            debuggerPaneHeader(
+          headers: <SizedBox>[
+            areaPaneHeader(
               context,
-              callStackTitle,
+              title: callStackTitle,
               needsTopBorder: false,
-              rightChild:
-                  // ignore: avoid_redundant_argument_values
-                  debugShowCallStackCount ? _callStackRightChild() : null,
+              actions: debugShowCallStackCount ? [_callStackRightChild()] : [],
             ),
-            debuggerPaneHeader(context, variablesTitle),
-            debuggerPaneHeader(
+            areaPaneHeader(context, title: variablesTitle),
+            areaPaneHeader(
               context,
-              breakpointsTitle,
-              rightChild: _breakpointsRightChild(),
+              title: breakpointsTitle,
+              actions: [
+                _breakpointsRightChild(),
+              ],
               rightPadding: 0.0,
             ),
           ],
@@ -254,8 +251,8 @@
         return Row(children: [
           BreakpointsCountBadge(breakpoints: breakpoints),
           ActionButton(
-            child: DebuggerToolbarAction(
-              Icons.delete,
+            child: ToolbarAction(
+              icon: Icons.delete,
               onPressed:
                   breakpoints.isNotEmpty ? controller.clearBreakpoints : null,
             ),
@@ -367,44 +364,3 @@
     return 'paused$reason$fileName $pos';
   }
 }
-
-FlexSplitColumnHeader debuggerPaneHeader(
-  BuildContext context,
-  String title, {
-  bool needsTopBorder = true,
-  Widget rightChild,
-  double rightPadding = 4.0,
-}) {
-  final theme = Theme.of(context);
-
-  return FlexSplitColumnHeader(
-    height: DebuggerScreen.debuggerPaneHeaderHeight,
-    child: Container(
-      decoration: BoxDecoration(
-        border: Border(
-          top: needsTopBorder
-              ? BorderSide(color: theme.focusColor)
-              : BorderSide.none,
-          bottom: BorderSide(color: theme.focusColor),
-        ),
-        color: titleSolidBackgroundColor(theme),
-      ),
-      padding: EdgeInsets.only(left: defaultSpacing, right: rightPadding),
-      alignment: Alignment.centerLeft,
-      height: DebuggerScreen.debuggerPaneHeaderHeight,
-      child: Row(
-        children: [
-          Expanded(
-            child: Text(
-              title,
-              maxLines: 1,
-              overflow: TextOverflow.ellipsis,
-              style: Theme.of(context).textTheme.subtitle2,
-            ),
-          ),
-          if (rightChild != null) rightChild,
-        ],
-      ),
-    ),
-  );
-}
diff --git a/packages/devtools_app/lib/src/debugger/scripts.dart b/packages/devtools_app/lib/src/debugger/scripts.dart
index c87a49b..e7250d7 100644
--- a/packages/devtools_app/lib/src/debugger/scripts.dart
+++ b/packages/devtools_app/lib/src/debugger/scripts.dart
@@ -69,14 +69,16 @@
     return OutlineDecoration(
       child: Column(
         children: [
-          debuggerPaneHeader(
+          areaPaneHeader(
             context,
-            'Libraries',
+            title: 'Libraries',
             needsTopBorder: false,
-            rightChild: CountBadge(
-              filteredItems: _filteredItems,
-              items: _items,
-            ),
+            actions: [
+              CountBadge(
+                filteredItems: _filteredItems,
+                items: _items,
+              ),
+            ],
           ),
           Container(
             decoration: BoxDecoration(
diff --git a/packages/devtools_app/lib/src/flex_split_column.dart b/packages/devtools_app/lib/src/flex_split_column.dart
index 032bb32..a549f8d 100644
--- a/packages/devtools_app/lib/src/flex_split_column.dart
+++ b/packages/devtools_app/lib/src/flex_split_column.dart
@@ -43,7 +43,7 @@
   /// creators of [FlexSplitColumn] can be unaware of the under-the-hood
   /// calculations necessary to achieve the UI requirements specified by
   /// [initialFractions] and [minSizes].
-  final List<FlexSplitColumnHeader> headers;
+  final List<SizedBox> headers;
 
   /// The children that will be laid out below each corresponding header in
   /// [headers].
@@ -71,7 +71,7 @@
   @visibleForTesting
   static List<Widget> buildChildrenWithFirstHeader(
     List<Widget> children,
-    List<FlexSplitColumnHeader> headers,
+    List<SizedBox> headers,
   ) {
     return [
       Column(
@@ -87,7 +87,7 @@
   @visibleForTesting
   static List<double> modifyInitialFractionsToIncludeFirstHeader(
     List<double> initialFractions,
-    List<FlexSplitColumnHeader> headers,
+    List<SizedBox> headers,
     double totalHeight,
   ) {
     var totalHeaderHeight = 0.0;
@@ -110,7 +110,7 @@
   @visibleForTesting
   static List<double> modifyMinSizesToIncludeFirstHeader(
     List<double> minSizes,
-    List<FlexSplitColumnHeader> headers,
+    List<SizedBox> headers,
   ) {
     return [
       minSizes[0] + headers[0].height,
@@ -129,11 +129,3 @@
     );
   }
 }
-
-// TODO(kenz): add support for arbitrarily sized widgets.
-class FlexSplitColumnHeader extends SizedBox {
-  const FlexSplitColumnHeader({
-    @required double height,
-    @required Widget child,
-  }) : super(height: height, child: child);
-}
diff --git a/packages/devtools_app/lib/src/logging/logging_screen.dart b/packages/devtools_app/lib/src/logging/logging_screen.dart
index 33ec5d5..3d8b125 100644
--- a/packages/devtools_app/lib/src/logging/logging_screen.dart
+++ b/packages/devtools_app/lib/src/logging/logging_screen.dart
@@ -130,7 +130,7 @@
       Expanded(
         child: Split(
           axis: Axis.vertical,
-          initialFractions: const [0.78, 0.22],
+          initialFractions: const [0.72, 0.28],
           children: [
             OutlineDecoration(
               child: LogsTable(
@@ -250,15 +250,21 @@
 
   Widget _buildSimpleLog(BuildContext context, LogData log) {
     final disabled = log?.details == null || log.details.isEmpty;
+
     return OutlineDecoration(
       child: Console(
+        title: areaPaneHeader(
+          context,
+          title: 'Details',
+          needsTopBorder: false,
+          actions: [
+            CopyToClipboardControl(
+              dataProvider: disabled ? null : () => log?.prettyPrinted,
+              buttonKey: LogDetails.copyToClipboardButtonKey,
+            ),
+          ],
+        ),
         lines: log?.prettyPrinted?.split('\n'),
-        controls: [
-          CopyToClipboardControl(
-            dataProvider: disabled ? null : () => log?.prettyPrinted,
-            buttonKey: LogDetails.copyToClipboardButtonKey,
-          ),
-        ],
       ),
     );
   }
diff --git a/packages/devtools_app/test/debugger_screen_test.dart b/packages/devtools_app/test/debugger_screen_test.dart
index 26c7a06..57a3bb6 100644
--- a/packages/devtools_app/test/debugger_screen_test.dart
+++ b/packages/devtools_app/test/debugger_screen_test.dart
@@ -128,7 +128,7 @@
 
         final clearStdioElement =
             find.byKey(DebuggerConsole.clearStdioButtonKey).evaluate().first;
-        final clearStdioButton = clearStdioElement.widget as IconButton;
+        final clearStdioButton = clearStdioElement.widget as ToolbarAction;
         expect(clearStdioButton.onPressed, isNull);
       });
 
diff --git a/packages/devtools_app/test/flex_split_column_test.dart b/packages/devtools_app/test/flex_split_column_test.dart
index 026d2a3..963d78e 100644
--- a/packages/devtools_app/test/flex_split_column_test.dart
+++ b/packages/devtools_app/test/flex_split_column_test.dart
@@ -15,10 +15,10 @@
     const children = [SizedBox(), SizedBox(), SizedBox(), SizedBox()];
     const firstHeaderKey = Key('first header');
     const headers = [
-      FlexSplitColumnHeader(height: 50.0, child: SizedBox(key: firstHeaderKey)),
-      FlexSplitColumnHeader(height: 50.0, child: SizedBox()),
-      FlexSplitColumnHeader(height: 50.0, child: SizedBox()),
-      FlexSplitColumnHeader(height: 50.0, child: SizedBox()),
+      SizedBox(height: 50.0, key: firstHeaderKey),
+      SizedBox(height: 50.0),
+      SizedBox(height: 50.0),
+      SizedBox(height: 50.0),
     ];
     const initialFractions = [0.25, 0.25, 0.25, 0.25];
     const minSizes = [10.0, 10.0, 10.0, 10.0];
diff --git a/packages/devtools_app/test/logging_screen_test.dart b/packages/devtools_app/test/logging_screen_test.dart
index a460b05..7305cdb 100644
--- a/packages/devtools_app/test/logging_screen_test.dart
+++ b/packages/devtools_app/test/logging_screen_test.dart
@@ -163,7 +163,7 @@
             .byKey(LogDetails.copyToClipboardButtonKey)
             .evaluate()
             .first
-            .widget as IconButton;
+            .widget as ToolbarAction;
 
         expect(
           copyButton().onPressed,