Commit 98ca156f authored by sadrul@chromium.org's avatar sadrul@chromium.org

athena: Add a minimized state for the home-card.

The home-card can be in a minimized state. In this state, only a narrow line
is displayed at the bottom of the screen, and dragging it up shows the overview
mode. When a window is selected in the overview mode, the home-card is minimized
again.

BUG=381224
R=mukai@chromium.org, oshima@chromium.org

Previously landed in r283539, reverted in r283564 because it broke tests.

Review URL: https://codereview.chromium.org/394043002

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@283804 0039d316-1c4b-4281-b951-d872f2087c98
parent ccc249fb
...@@ -45,6 +45,8 @@ ...@@ -45,6 +45,8 @@
'home/app_list_view_delegate.cc', 'home/app_list_view_delegate.cc',
'home/app_list_view_delegate.h', 'home/app_list_view_delegate.h',
'home/home_card_impl.cc', 'home/home_card_impl.cc',
'home/minimized_home.cc',
'home/minimized_home.h',
'home/public/app_model_builder.h', 'home/public/app_model_builder.h',
'home/public/home_card.h', 'home/public/home_card.h',
'input/accelerator_manager_impl.cc', 'input/accelerator_manager_impl.cc',
...@@ -59,6 +61,7 @@ ...@@ -59,6 +61,7 @@
'screen/screen_accelerator_handler.h', 'screen/screen_accelerator_handler.h',
'screen/screen_manager_impl.cc', 'screen/screen_manager_impl.cc',
'wm/public/window_manager.h', 'wm/public/window_manager.h',
'wm/public/window_manager_observer.h',
'wm/window_manager_impl.cc', 'wm/window_manager_impl.cc',
'wm/window_overview_mode.cc', 'wm/window_overview_mode.cc',
'wm/window_overview_mode.h', 'wm/window_overview_mode.h',
......
include_rules = [ include_rules = [
"+athena/input/public", "+athena/input/public",
"+athena/screen/public", "+athena/screen/public",
"+athena/wm/public",
"+third_party/skia/include", "+third_party/skia/include",
"+ui/aura", "+ui/aura",
"+ui/app_list", "+ui/app_list",
......
...@@ -5,13 +5,18 @@ ...@@ -5,13 +5,18 @@
#include "athena/home/public/home_card.h" #include "athena/home/public/home_card.h"
#include "athena/home/app_list_view_delegate.h" #include "athena/home/app_list_view_delegate.h"
#include "athena/home/minimized_home.h"
#include "athena/home/public/app_model_builder.h" #include "athena/home/public/app_model_builder.h"
#include "athena/input/public/accelerator_manager.h" #include "athena/input/public/accelerator_manager.h"
#include "athena/screen/public/screen_manager.h" #include "athena/screen/public/screen_manager.h"
#include "athena/wm/public/window_manager.h"
#include "athena/wm/public/window_manager_observer.h"
#include "base/bind.h"
#include "ui/app_list/search_provider.h" #include "ui/app_list/search_provider.h"
#include "ui/app_list/views/app_list_view.h" #include "ui/app_list/views/app_list_view.h"
#include "ui/aura/layout_manager.h" #include "ui/aura/layout_manager.h"
#include "ui/aura/window.h" #include "ui/aura/window.h"
#include "ui/views/layout/box_layout.h"
#include "ui/wm/core/visibility_controller.h" #include "ui/wm/core/visibility_controller.h"
#include "ui/wm/core/window_animations.h" #include "ui/wm/core/window_animations.h"
...@@ -20,10 +25,27 @@ namespace { ...@@ -20,10 +25,27 @@ namespace {
HomeCard* instance = NULL; HomeCard* instance = NULL;
// Makes sure the homecard is center-aligned horizontally and bottom-aligned
// vertically.
class HomeCardLayoutManager : public aura::LayoutManager { class HomeCardLayoutManager : public aura::LayoutManager {
public: public:
explicit HomeCardLayoutManager(aura::Window* container) class Delegate {
: container_(container) {} public:
virtual ~Delegate() {}
virtual int GetHomeCardHeight() const = 0;
virtual int GetHorizontalMargin() const = 0;
// TODO(mukai): Remove this when bubble is no longer used for
// VISIBLE_CENTERED or VISIBLE_BOTTOM states.
virtual bool HasShadow() const = 0;
virtual aura::Window* GetNativeWindow() = 0;
};
explicit HomeCardLayoutManager(Delegate* delegate)
: delegate_(delegate) {}
virtual ~HomeCardLayoutManager() {} virtual ~HomeCardLayoutManager() {}
void UpdateVirtualKeyboardBounds(const gfx::Rect& bounds) { void UpdateVirtualKeyboardBounds(const gfx::Rect& bounds) {
...@@ -49,37 +71,42 @@ class HomeCardLayoutManager : public aura::LayoutManager { ...@@ -49,37 +71,42 @@ class HomeCardLayoutManager : public aura::LayoutManager {
} }
void Layout() { void Layout() {
const int kHomeCardHeight = 150; int height = delegate_->GetHomeCardHeight();
const int kHomeCardHorizontalMargin = 50; int horiz_margin = delegate_->GetHorizontalMargin();
// Currently the home card is provided as a bubble and the bounds has to be aura::Window* home_card = delegate_->GetNativeWindow();
// increased to cancel the shadow. // |home_card| could be detached from the root window (e.g. when it is being
// TODO(mukai): stops using the bubble and remove this. // destroyed).
const int kHomeCardShadowWidth = 30; if (!home_card || !home_card->GetRootWindow())
if (container_->children().size() < 1)
return;
aura::Window* home_card = container_->children()[0];
if (!home_card->IsVisible())
return; return;
gfx::Rect screen_bounds = home_card->GetRootWindow()->bounds(); gfx::Rect screen_bounds = home_card->GetRootWindow()->bounds();
if (!virtual_keyboard_bounds_.IsEmpty()) if (!virtual_keyboard_bounds_.IsEmpty())
screen_bounds.set_height(virtual_keyboard_bounds_.y()); screen_bounds.set_height(virtual_keyboard_bounds_.y());
gfx::Rect card_bounds = screen_bounds; gfx::Rect card_bounds = screen_bounds;
card_bounds.Inset(kHomeCardHorizontalMargin, card_bounds.Inset(horiz_margin, screen_bounds.height() - height,
screen_bounds.height() - kHomeCardHeight, horiz_margin, 0);
kHomeCardHorizontalMargin,
0); if (delegate_->HasShadow()) {
card_bounds.Inset(-kHomeCardShadowWidth, -kHomeCardShadowWidth); // Currently the home card is provided as a bubble and the bounds has to
// be increased to cancel the shadow.
// TODO(mukai): stops using the bubble and remove this.
const int kHomeCardShadowWidth = 30;
card_bounds.Inset(-kHomeCardShadowWidth, -kHomeCardShadowWidth);
}
SetChildBoundsDirect(home_card, card_bounds); SetChildBoundsDirect(home_card, card_bounds);
} }
aura::Window* container_; Delegate* delegate_;
gfx::Rect virtual_keyboard_bounds_; gfx::Rect virtual_keyboard_bounds_;
DISALLOW_COPY_AND_ASSIGN(HomeCardLayoutManager); DISALLOW_COPY_AND_ASSIGN(HomeCardLayoutManager);
}; };
class HomeCardImpl : public HomeCard, public AcceleratorHandler { class HomeCardImpl : public HomeCard,
public AcceleratorHandler,
public HomeCardLayoutManager::Delegate,
public MinimizedHomeDragDelegate,
public WindowManagerObserver {
public: public:
explicit HomeCardImpl(AppModelBuilder* model_builder); explicit HomeCardImpl(AppModelBuilder* model_builder);
virtual ~HomeCardImpl(); virtual ~HomeCardImpl();
...@@ -111,11 +138,59 @@ class HomeCardImpl : public HomeCard, public AcceleratorHandler { ...@@ -111,11 +138,59 @@ class HomeCardImpl : public HomeCard, public AcceleratorHandler {
return true; return true;
} }
// HomeCardLayoutManager::Delegate:
virtual int GetHomeCardHeight() const OVERRIDE {
const int kHomeCardHeight = 150;
const int kHomeCardMinimizedHeight = 8;
CHECK_NE(HIDDEN, state_);
return state_ == VISIBLE_MINIMIZED ? kHomeCardMinimizedHeight :
kHomeCardHeight;
}
virtual int GetHorizontalMargin() const OVERRIDE {
CHECK_NE(HIDDEN, state_);
const int kHomeCardHorizontalMargin = 50;
return state_ == VISIBLE_MINIMIZED ? 0 : kHomeCardHorizontalMargin;
}
virtual bool HasShadow() const OVERRIDE {
CHECK_NE(HIDDEN, state_);
return state_ != VISIBLE_MINIMIZED;
}
virtual aura::Window* GetNativeWindow() OVERRIDE {
switch (state_) {
case HIDDEN:
return NULL;
case VISIBLE_MINIMIZED:
return minimized_widget_ ? minimized_widget_->GetNativeWindow() : NULL;
case VISIBLE_CENTERED:
case VISIBLE_BOTTOM:
return home_card_widget_ ? home_card_widget_->GetNativeWindow() : NULL;
}
return NULL;
}
// MinimizedHomeDragDelegate:
virtual void OnDragUpCompleted() OVERRIDE {
WindowManager::GetInstance()->ToggleOverview();
}
// WindowManagerObserver:
virtual void OnOverviewModeEnter() OVERRIDE {
SetState(VISIBLE_BOTTOM);
}
virtual void OnOverviewModeExit() OVERRIDE {
SetState(VISIBLE_MINIMIZED);
}
scoped_ptr<AppModelBuilder> model_builder_; scoped_ptr<AppModelBuilder> model_builder_;
HomeCard::State state_; HomeCard::State state_;
views::Widget* home_card_widget_; views::Widget* home_card_widget_;
views::Widget* minimized_widget_;
AppListViewDelegate* view_delegate_; AppListViewDelegate* view_delegate_;
HomeCardLayoutManager* layout_manager_; HomeCardLayoutManager* layout_manager_;
...@@ -128,26 +203,43 @@ class HomeCardImpl : public HomeCard, public AcceleratorHandler { ...@@ -128,26 +203,43 @@ class HomeCardImpl : public HomeCard, public AcceleratorHandler {
HomeCardImpl::HomeCardImpl(AppModelBuilder* model_builder) HomeCardImpl::HomeCardImpl(AppModelBuilder* model_builder)
: model_builder_(model_builder), : model_builder_(model_builder),
state_(HIDDEN), state_(VISIBLE_MINIMIZED),
home_card_widget_(NULL), home_card_widget_(NULL),
minimized_widget_(NULL),
layout_manager_(NULL) { layout_manager_(NULL) {
DCHECK(!instance); DCHECK(!instance);
instance = this; instance = this;
WindowManager::GetInstance()->AddObserver(this);
} }
HomeCardImpl::~HomeCardImpl() { HomeCardImpl::~HomeCardImpl() {
DCHECK(instance); DCHECK(instance);
WindowManager::GetInstance()->RemoveObserver(this);
home_card_widget_->CloseNow(); home_card_widget_->CloseNow();
minimized_widget_->CloseNow();
view_delegate_ = NULL; view_delegate_ = NULL;
instance = NULL; instance = NULL;
} }
void HomeCardImpl::SetState(HomeCard::State state) { void HomeCardImpl::SetState(HomeCard::State state) {
if (state == HIDDEN) // Update |state_| before changing the visibility of the widgets, so that
home_card_widget_->Hide(); // LayoutManager callbacks get the correct state.
else
home_card_widget_->Show();
state_ = state; state_ = state;
switch (state_) {
case VISIBLE_MINIMIZED:
home_card_widget_->Hide();
minimized_widget_->Show();
break;
case HIDDEN:
home_card_widget_->Hide();
minimized_widget_->Hide();
break;
case VISIBLE_BOTTOM:
case VISIBLE_CENTERED:
home_card_widget_->Show();
minimized_widget_->Hide();
break;
}
} }
void HomeCardImpl::RegisterSearchProvider( void HomeCardImpl::RegisterSearchProvider(
...@@ -159,6 +251,12 @@ void HomeCardImpl::RegisterSearchProvider( ...@@ -159,6 +251,12 @@ void HomeCardImpl::RegisterSearchProvider(
void HomeCardImpl::UpdateVirtualKeyboardBounds( void HomeCardImpl::UpdateVirtualKeyboardBounds(
const gfx::Rect& bounds) { const gfx::Rect& bounds) {
if (state_ == VISIBLE_MINIMIZED) {
if (bounds.IsEmpty())
minimized_widget_->Show();
else
minimized_widget_->Hide();
}
layout_manager_->UpdateVirtualKeyboardBounds(bounds); layout_manager_->UpdateVirtualKeyboardBounds(bounds);
} }
...@@ -167,7 +265,7 @@ void HomeCardImpl::Init() { ...@@ -167,7 +265,7 @@ void HomeCardImpl::Init() {
aura::Window* container = aura::Window* container =
ScreenManager::Get()->CreateContainer("HomeCardContainer"); ScreenManager::Get()->CreateContainer("HomeCardContainer");
layout_manager_ = new HomeCardLayoutManager(container); layout_manager_ = new HomeCardLayoutManager(this);
container->SetLayoutManager(layout_manager_); container->SetLayoutManager(layout_manager_);
wm::SetChildWindowVisibilityChangesAnimated(container); wm::SetChildWindowVisibilityChangesAnimated(container);
...@@ -182,9 +280,10 @@ void HomeCardImpl::Init() { ...@@ -182,9 +280,10 @@ void HomeCardImpl::Init() {
views::BubbleBorder::FLOAT, views::BubbleBorder::FLOAT,
true /* border_accepts_events */); true /* border_accepts_events */);
home_card_widget_ = view->GetWidget(); home_card_widget_ = view->GetWidget();
// TODO: the initial value might not be visible.
state_ = VISIBLE_CENTERED; // Start off in the minimized state.
view->ShowWhenReady(); minimized_widget_ = CreateMinimizedHome(container, this);
SetState(VISIBLE_MINIMIZED);
} }
void HomeCardImpl::InstallAccelerators() { void HomeCardImpl::InstallAccelerators() {
......
// Copyright 2014 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 "athena/home/minimized_home.h"
#include "ui/views/background.h"
#include "ui/views/layout/box_layout.h"
#include "ui/views/view.h"
#include "ui/views/widget/widget.h"
namespace {
const SkColor kDragHandleColorNormal = SK_ColorGRAY;
const SkColor kDragHandleColorHot = SK_ColorWHITE;
class MinimizedHomeView : public views::View {
public:
explicit MinimizedHomeView(athena::MinimizedHomeDragDelegate* delegate)
: delegate_(delegate),
color_(SK_ColorTRANSPARENT) {
SetColor(kDragHandleColorNormal);
}
virtual ~MinimizedHomeView() {}
private:
void SetColor(SkColor color) {
if (color_ == color)
return;
color_ = color;
set_background(views::Background::CreateSolidBackground(color_));
SchedulePaint();
}
// views::View:
virtual gfx::Size GetPreferredSize() const OVERRIDE {
const int kDragHandleWidth = 80;
const int kDragHandleHeight = 4;
return gfx::Size(kDragHandleWidth, kDragHandleHeight);
}
virtual void OnGestureEvent(ui::GestureEvent* event) OVERRIDE {
SkColor change_color = SK_ColorTRANSPARENT;
if (event->type() == ui::ET_GESTURE_BEGIN &&
event->details().touch_points() == 1) {
change_color = kDragHandleColorHot;
} else if (event->type() == ui::ET_GESTURE_END &&
event->details().touch_points() == 1) {
change_color = kDragHandleColorNormal;
}
if (change_color != SK_ColorTRANSPARENT) {
SetColor(change_color);
event->SetHandled();
return;
}
if (event->type() == ui::ET_GESTURE_SCROLL_BEGIN) {
event->SetHandled();
} else if (event->type() == ui::ET_SCROLL_FLING_START) {
const ui::GestureEventDetails& details = event->details();
const float kFlingCompletionVelocity = -100.f;
if (details.velocity_y() < kFlingCompletionVelocity)
delegate_->OnDragUpCompleted();
SetColor(kDragHandleColorNormal);
}
}
athena::MinimizedHomeDragDelegate* delegate_;
SkColor color_;
DISALLOW_COPY_AND_ASSIGN(MinimizedHomeView);
};
} // namespace
namespace athena {
views::Widget* CreateMinimizedHome(aura::Window* container,
MinimizedHomeDragDelegate* delegate) {
views::Widget* widget = new views::Widget;
views::Widget::InitParams params(
views::Widget::InitParams::TYPE_WINDOW_FRAMELESS);
params.parent = container;
params.delegate = NULL;
widget->Init(params);
views::View* content_view = new views::View;
widget->SetContentsView(content_view);
views::BoxLayout* layout =
new views::BoxLayout(views::BoxLayout::kHorizontal, 0, 2, 0);
layout->set_main_axis_alignment(views::BoxLayout::MAIN_AXIS_ALIGNMENT_CENTER);
content_view->SetLayoutManager(layout);
views::View* view = new MinimizedHomeView(delegate);
content_view->AddChildView(view);
return widget;
}
} // namespace athena
// Copyright 2014 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.
#ifndef ATHENA_HOME_MINIMIZED_HOME_H_
#define ATHENA_HOME_MINIMIZED_HOME_H_
namespace aura {
class Window;
}
namespace views {
class Widget;
}
namespace athena {
class MinimizedHomeDragDelegate {
public:
virtual ~MinimizedHomeDragDelegate() {}
virtual void OnDragUpCompleted() = 0;
};
// Note that |delegate| is guaranteed to be alive as long as the returned widget
// is alive.
views::Widget* CreateMinimizedHome(aura::Window* container,
MinimizedHomeDragDelegate* delegate);
} // namespace athena
#endif // ATHENA_HOME_MINIMIZED_HOME_H_
...@@ -30,6 +30,10 @@ class ATHENA_EXPORT HomeCard { ...@@ -30,6 +30,10 @@ class ATHENA_EXPORT HomeCard {
// HomeCard is visible smaller at the bottom of the screen as a supplemental // HomeCard is visible smaller at the bottom of the screen as a supplemental
// widget. // widget.
VISIBLE_BOTTOM, VISIBLE_BOTTOM,
// HomeCard is minimized (i.e. a small UI element is displayed on screen
// that the user can interact with to bring up the BOTTOM or CENTERED view).
VISIBLE_MINIMIZED,
}; };
// Creates/deletes/gets the singleton object of the HomeCard // Creates/deletes/gets the singleton object of the HomeCard
...@@ -48,7 +52,8 @@ class ATHENA_EXPORT HomeCard { ...@@ -48,7 +52,8 @@ class ATHENA_EXPORT HomeCard {
virtual void RegisterSearchProvider( virtual void RegisterSearchProvider(
app_list::SearchProvider* search_provider) = 0; app_list::SearchProvider* search_provider) = 0;
// Called when the virtual keyboard changed has changed to |bounds|. // Called when the virtual keyboard changed has changed to |bounds|. An empty
// |bounds| indicates that the virtual keyboard is not visible anymore.
virtual void UpdateVirtualKeyboardBounds( virtual void UpdateVirtualKeyboardBounds(
const gfx::Rect& bounds) = 0; const gfx::Rect& bounds) = 0;
}; };
......
...@@ -9,6 +9,8 @@ ...@@ -9,6 +9,8 @@
namespace athena { namespace athena {
class WindowManagerObserver;
// Manages the application, web windows. // Manages the application, web windows.
class ATHENA_EXPORT WindowManager { class ATHENA_EXPORT WindowManager {
public: public:
...@@ -16,10 +18,14 @@ class ATHENA_EXPORT WindowManager { ...@@ -16,10 +18,14 @@ class ATHENA_EXPORT WindowManager {
// implementation. // implementation.
static WindowManager* Create(); static WindowManager* Create();
static void Shutdown(); static void Shutdown();
static WindowManager* GetInstance();
virtual ~WindowManager() {} virtual ~WindowManager() {}
virtual void ToggleOverview() = 0; virtual void ToggleOverview() = 0;
virtual void AddObserver(WindowManagerObserver* observer) = 0;
virtual void RemoveObserver(WindowManagerObserver* observer) = 0;
}; };
} // namespace athena } // namespace athena
......
// Copyright 2014 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.
#ifndef ATHENA_SCREEN_PUBLIC_WINDOW_MANAGER_OBSERVER_H_
#define ATHENA_SCREEN_PUBLIC_WINDOW_MANAGER_OBSERVER_H_
#include "athena/athena_export.h"
namespace athena {
class ATHENA_EXPORT WindowManagerObserver {
public:
virtual ~WindowManagerObserver() {}
// Called when the overview mode is displayed.
virtual void OnOverviewModeEnter() = 0;
// Called when going out of overview mode.
virtual void OnOverviewModeExit() = 0;
};
} // namespace athena
#endif // ATHENA_SCREEN_PUBLIC_WINDOW_MANAGER_OBSERVER_H_
...@@ -6,8 +6,10 @@ ...@@ -6,8 +6,10 @@
#include "athena/input/public/accelerator_manager.h" #include "athena/input/public/accelerator_manager.h"
#include "athena/screen/public/screen_manager.h" #include "athena/screen/public/screen_manager.h"
#include "athena/wm/public/window_manager_observer.h"
#include "athena/wm/window_overview_mode.h" #include "athena/wm/window_overview_mode.h"
#include "base/logging.h" #include "base/logging.h"
#include "base/observer_list.h"
#include "ui/aura/layout_manager.h" #include "ui/aura/layout_manager.h"
#include "ui/aura/window.h" #include "ui/aura/window.h"
#include "ui/wm/public/window_types.h" #include "ui/wm/public/window_types.h"
...@@ -29,10 +31,15 @@ class WindowManagerImpl : public WindowManager, ...@@ -29,10 +31,15 @@ class WindowManagerImpl : public WindowManager,
// WindowManager: // WindowManager:
virtual void ToggleOverview() OVERRIDE { virtual void ToggleOverview() OVERRIDE {
if (overview_) if (overview_) {
overview_.reset(); overview_.reset();
else FOR_EACH_OBSERVER(WindowManagerObserver, observers_,
OnOverviewModeExit());
} else {
overview_ = WindowOverviewMode::Create(container_.get(), this); overview_ = WindowOverviewMode::Create(container_.get(), this);
FOR_EACH_OBSERVER(WindowManagerObserver, observers_,
OnOverviewModeEnter());
}
} }
private: private:
...@@ -49,11 +56,22 @@ class WindowManagerImpl : public WindowManager, ...@@ -49,11 +56,22 @@ class WindowManagerImpl : public WindowManager,
accelerator_data, arraysize(accelerator_data), this); accelerator_data, arraysize(accelerator_data), this);
} }
// WindowManager:
virtual void AddObserver(WindowManagerObserver* observer) OVERRIDE {
observers_.AddObserver(observer);
}
virtual void RemoveObserver(WindowManagerObserver* observer) OVERRIDE {
observers_.RemoveObserver(observer);
}
// WindowOverviewModeDelegate: // WindowOverviewModeDelegate:
virtual void OnSelectWindow(aura::Window* window) OVERRIDE { virtual void OnSelectWindow(aura::Window* window) OVERRIDE {
CHECK_EQ(container_.get(), window->parent()); CHECK_EQ(container_.get(), window->parent());
container_->StackChildAtTop(window); container_->StackChildAtTop(window);
overview_.reset(); overview_.reset();
FOR_EACH_OBSERVER(WindowManagerObserver, observers_,
OnOverviewModeExit());
} }
// aura::WindowObserver // aura::WindowObserver
...@@ -76,6 +94,7 @@ class WindowManagerImpl : public WindowManager, ...@@ -76,6 +94,7 @@ class WindowManagerImpl : public WindowManager,
scoped_ptr<aura::Window> container_; scoped_ptr<aura::Window> container_;
scoped_ptr<WindowOverviewMode> overview_; scoped_ptr<WindowOverviewMode> overview_;
ObserverList<WindowManagerObserver> observers_;
DISALLOW_COPY_AND_ASSIGN(WindowManagerImpl); DISALLOW_COPY_AND_ASSIGN(WindowManagerImpl);
}; };
...@@ -156,4 +175,10 @@ void WindowManager::Shutdown() { ...@@ -156,4 +175,10 @@ void WindowManager::Shutdown() {
DCHECK(!instance); DCHECK(!instance);
} }
// static
WindowManager* WindowManager::GetInstance() {
DCHECK(instance);
return instance;
}
} // namespace athena } // namespace athena
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment