summaryrefslogtreecommitdiffstats
path: root/chrome/browser/chromeos/views/menu_locator.cc
blob: 6dc81485eea05be24d312a264ade080cbef47765 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
// Copyright (c) 2011 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "chrome/browser/chromeos/views/menu_locator.h"

#include "base/i18n/rtl.h"
#include "base/logging.h"
#include "chrome/browser/chromeos/views/webui_menu_widget.h"
#include "gfx/point.h"
#include "gfx/rect.h"
#include "gfx/insets.h"
#include "views/screen.h"
#include "views/widget/widget.h"

namespace {

using chromeos::WebUIMenuWidget;

// Menu's corner radious.
const int kMenuCornerRadius = 0;  // crosbug.com/7718.
const int kSubmenuOverlapPx = 1;

gfx::Rect GetBoundsOf(const views::Widget* widget) {
  gfx::Rect bounds;
  widget->GetBounds(&bounds, false);
  return bounds;
}

// Returns the Rect of the screen that contains the point (x, y).
gfx::Rect GetScreenRectAt(int x, int y) {
  return views::Screen::GetMonitorAreaNearestPoint(gfx::Point(x, y));
}

// Returns adjusted height of the menu that fits to the screen's
// height, and enables scrolling if necessary.
int AdjustHeight(WebUIMenuWidget* widget,
                 int screen_height,
                 int height) {
  // TODO(oshima): Locator needs a preferred size so that
  // 1) we can tell height == screen_rect is the result of
  //    locator resizing it, or preferred size happens to be
  //    same hight of the screen (which is rare).
  // 2) when the menu is moved to place where it has more space, it can
  //    hide the scrollbar again. (which won't happen on chromeos now)
  if (height >= screen_height) {
    widget->EnableScroll(true);
    return screen_height;
  }
  widget->EnableScroll(false);
  return height;
}

// Updates the root menu's bounds to fit to the screen.
void UpdateRootMenuBounds(WebUIMenuWidget* widget,
                          const gfx::Point& origin,
                          const gfx::Size& size,
                          bool align_right) {
  gfx::Rect screen_rect = GetScreenRectAt(origin.x(), origin.y());
  int width = std::min(screen_rect.width(), size.width());
  int height = AdjustHeight(widget, screen_rect.height(), size.height());

  int x = align_right ? origin.x() - width : origin.x();
  int y = origin.y();
  if (x + width > screen_rect.right())
    x = screen_rect.right() - width;
  if (y + height > screen_rect.bottom())
    y = screen_rect.bottom() - height;
  widget->SetBounds(gfx::Rect(x, y, width, height));
}

// MenuLocator for dropdown menu.
class DropDownMenuLocator : public chromeos::MenuLocator {
 public:
  explicit DropDownMenuLocator(const gfx::Point& origin)
      : origin_(origin) {
  }

 private:
  virtual SubmenuDirection GetSubmenuDirection() const {
    return DEFAULT;
  }

  virtual void Move(WebUIMenuWidget* widget) {
    // TODO(oshima):
    // Dropdown Menu has to be shown above the button, which is not currently
    // possible with Menu2. I'll update Menu2 and this code
    // after beta.
    gfx::Rect bounds;
    widget->GetBounds(&bounds, false);
    UpdateRootMenuBounds(widget, origin_, bounds.size(), !base::i18n::IsRTL());
  }

  virtual void SetBounds(WebUIMenuWidget* widget, const gfx::Size& size) {
    gfx::Size new_size(size);
    new_size.Enlarge(0, kMenuCornerRadius);
    UpdateRootMenuBounds(widget, origin_, size, !base::i18n::IsRTL());
  }

  virtual void GetInsets(gfx::Insets* insets) const {
    insets->Set(0, 0, kMenuCornerRadius, 0);
  }

  virtual const SkScalar* GetCorners() const {
    static const SkScalar corners[] = {
      0, 0,
      0, 0,
      kMenuCornerRadius, kMenuCornerRadius,
      kMenuCornerRadius, kMenuCornerRadius,
    };
    return corners;
  }

  gfx::Point origin_;

  DISALLOW_COPY_AND_ASSIGN(DropDownMenuLocator);
};

// MenuLocator for context menu.
class ContextMenuLocator : public chromeos::MenuLocator {
 public:
  explicit ContextMenuLocator(const gfx::Point& origin)
      : origin_(origin) {
  }

 private:
  virtual SubmenuDirection GetSubmenuDirection() const {
    return DEFAULT;
  }

  virtual void Move(WebUIMenuWidget* widget) {
    gfx::Rect bounds;
    widget->GetBounds(&bounds, false);
    UpdateRootMenuBounds(widget, origin_, bounds.size(), base::i18n::IsRTL());
  }

  virtual void SetBounds(WebUIMenuWidget* widget, const gfx::Size& size) {
    gfx::Size new_size(size);
    new_size.Enlarge(0, kMenuCornerRadius * 2);
    UpdateRootMenuBounds(widget, origin_, new_size, base::i18n::IsRTL());
  }

  virtual const SkScalar* GetCorners() const {
    static const SkScalar corners[] = {
      kMenuCornerRadius, kMenuCornerRadius,
      kMenuCornerRadius, kMenuCornerRadius,
      kMenuCornerRadius, kMenuCornerRadius,
      kMenuCornerRadius, kMenuCornerRadius,
    };
    return corners;
  }

  virtual void GetInsets(gfx::Insets* insets) const {
    insets->Set(kMenuCornerRadius, 0, kMenuCornerRadius, 0);
  }

  gfx::Point origin_;

  DISALLOW_COPY_AND_ASSIGN(ContextMenuLocator);
};

// MenuLocator for submenu.
class SubMenuLocator : public chromeos::MenuLocator {
 public:
  SubMenuLocator(const WebUIMenuWidget* parent,
                 MenuLocator::SubmenuDirection parent_direction,
                 int y)
      : parent_rect_(GetBoundsOf(parent)),
        parent_direction_(parent_direction),
        root_y_(parent_rect_.y() + y),
        corners_(NULL),
        direction_(DEFAULT) {
  }

 private:
  virtual SubmenuDirection GetSubmenuDirection() const {
    return direction_;
  }

  virtual void Move(WebUIMenuWidget* widget) {
    gfx::Rect bounds;
    widget->GetBounds(&bounds, false);
    UpdateBounds(widget, bounds.size());
  }

  virtual void SetBounds(WebUIMenuWidget* widget, const gfx::Size& size) {
    gfx::Size new_size(size);
    new_size.Enlarge(0, kMenuCornerRadius * 2);
    UpdateBounds(widget, new_size);
  }

  virtual const SkScalar* GetCorners() const {
    return corners_;
  }

  virtual void GetInsets(gfx::Insets* insets) const {
    insets->Set(kMenuCornerRadius, 0, kMenuCornerRadius, 0);
  }

  // Rounded corner definitions for right/left attached submenu.
  static const SkScalar kRightCorners[];
  static const SkScalar kLeftCorners[];

  void UpdateBounds(WebUIMenuWidget* widget, const gfx::Size& size) {
    gfx::Rect screen_rect = GetScreenRectAt(parent_rect_.x(), root_y_);
    int width = std::min(screen_rect.width(), size.width());
    int height = AdjustHeight(widget, size.height(), screen_rect.height());

    SubmenuDirection direction = parent_direction_;
    if (direction == DEFAULT) {
      if (base::i18n::IsRTL()) {
        direction = LEFT;
      } else {
        direction = RIGHT;
      }
    }
    // Adjust Y to fit the screen.
    int y = root_y_;
    if (root_y_ + height > screen_rect.bottom())
      y = screen_rect.bottom() - height;
    // Determine the attachment.
    // TODO(oshima):
    // Come up with better placement when menu is wide,
    // probably limit max width and let each menu scroll
    // horizontally when selected.
    int x = direction == RIGHT ?
        ComputeXToRight(screen_rect, width) :
        ComputeXToLeft(screen_rect, width);
    corners_ = direction_ == RIGHT ? kRightCorners : kLeftCorners;
    widget->SetBounds(gfx::Rect(x, y, width, height));
  }

  int ComputeXToRight(const gfx::Rect& screen_rect, int width) {
    if (parent_rect_.right() + width > screen_rect.right()) {
      if (parent_rect_.x() - width < screen_rect.x()) {
        // Place on the right to fit to the screen if no space on left
        direction_ = RIGHT;
        return screen_rect.right() - width;
      }
      direction_ = LEFT;
      return parent_rect_.x() - width + kSubmenuOverlapPx;
    } else {
      direction_ = RIGHT;
      return parent_rect_.right() - kSubmenuOverlapPx;
    }
  }

  int ComputeXToLeft(const gfx::Rect& screen_rect, int width) {
    if (parent_rect_.x() - width < screen_rect.x()) {
      if (parent_rect_.right() + width > screen_rect.right()) {
        // no space on right
        direction_ = LEFT;
        return screen_rect.x();
      }
      corners_ = kRightCorners;
      direction_ = RIGHT;
      return parent_rect_.right() - kSubmenuOverlapPx;
    } else {
      corners_ = kLeftCorners;
      direction_ = LEFT;
      return parent_rect_.x() - width + kSubmenuOverlapPx;
    }
  }

  const gfx::Rect parent_rect_;

  const MenuLocator::SubmenuDirection parent_direction_;

  const int root_y_;

  SkScalar const* corners_;

  // The direction the this menu is attached to its parent. Submenu may still
  // choose different direction if there is no spece for that direction
  // (2nd turnaround).
  SubmenuDirection direction_;

  DISALLOW_COPY_AND_ASSIGN(SubMenuLocator);
};

// Rounded corners of the submenu attached to right side.
const SkScalar SubMenuLocator::kRightCorners[] = {
  0, 0,
  kMenuCornerRadius, kMenuCornerRadius,
  kMenuCornerRadius, kMenuCornerRadius,
  kMenuCornerRadius, kMenuCornerRadius,
};

// Rounded corners of the submenu attached to left side.
const SkScalar SubMenuLocator::kLeftCorners[] = {
  kMenuCornerRadius, kMenuCornerRadius,
  0, 0,
  kMenuCornerRadius, kMenuCornerRadius,
  kMenuCornerRadius, kMenuCornerRadius,
};


}  // namespace

namespace chromeos {

// static
MenuLocator* MenuLocator::CreateDropDownMenuLocator(const gfx::Point& p) {
  return new DropDownMenuLocator(p);
}

MenuLocator* MenuLocator::CreateContextMenuLocator(const gfx::Point& p) {
  return new ContextMenuLocator(p);
}

MenuLocator* MenuLocator::CreateSubMenuLocator(
    const WebUIMenuWidget* parent,
    MenuLocator::SubmenuDirection parent_direction,
    int y) {
  return new SubMenuLocator(parent, parent_direction, y);
}

}  // namespace chromeos