Compare commits

..

23 Commits

Author SHA1 Message Date
33b30114a4 QDoc port 2024-09-14 16:01:55 +02:00
2dbd1d6eb7 Update Frameworks version requirement to 6.5.0
GIT_SILENT
2024-09-12 11:08:45 +01:00
0d11058c8f update version for new release 2024-09-12 10:50:16 +01:00
a5fbaa5975 Reformat CMake code for better readability 2024-08-14 11:08:41 +02:00
6f8a3f5d42 Generate wayland code with PRIVATE_CODE 2024-08-14 10:47:07 +02:00
303f68cf10 Update Qt version requirement to 6.7.0
GIT_SILENT
2024-08-02 10:15:46 +01:00
368cf2dd37 Port to QWaylandWindow::updateExposure() 2024-07-05 09:49:38 +03:00
16a1debdca Port to QWaylandShellSurface::setWindowSize() 2024-06-26 13:01:50 +03:00
c4987c01c7 update version for new release 2024-05-24 16:14:22 +01:00
bfdf40b1b5 Update Qt version requirement to 6.6.0
GIT_SILENT
2024-05-24 13:11:58 +01:00
8676cf98ce update version for new release 2024-05-24 12:32:48 +01:00
16da49d3a9 Update Qt version requirement to 6.7.0
GIT_SILENT
2024-05-24 11:22:43 +01:00
97d85c8722 Update Frameworks version requirement to 6.2.0
GIT_SILENT
2024-05-24 11:22:43 +01:00
44e011e2c6 declarative: Add a QML extension object for accessing the margins property
Otherwise we get the following error message:
Property "margins" with type "QMargins", which is not a value type

Signed-off-by: Victoria Fischer <victoria.fischer@mbition.io>
2024-05-20 10:01:24 +00:00
05de6c67b2 Enforce passing tests on all platforms 2024-04-22 23:45:49 +02:00
7c4706c415 Add clang-format commit hook 2024-04-21 17:51:44 +02:00
80a047c0db Run clang-format 2024-04-21 17:50:57 +02:00
2ac46d8d9d Drop now defunct code to sync resizes
This code was designed to make sure we didn't commit new buffers whilst
we were waiting for a configure. The way this worked failed in 6 after
kwin does not reply to desired_size changes immediately.

It is uneeded after kwin commit "wayland: Avoid rearranging layer
surfaces when wl_surface size changes" which means if we do submit
frames between size change requests, they'll be ignored. Meaning the
client will eventually get a configure event at the right size.
2024-04-11 17:57:14 +01:00
e8594be884 Use QWaylandWindow::windowContentGeometry() to set the initial preferred size
This matches the geometry used in the setWindowGeometry() function.
2024-04-04 14:18:38 +03:00
0a31923938 Update the desired size when the anchors change
When the anchor changes, the desired size should be updated to avoid
the compositor posting a protocol error.

BUG: 484990
2024-04-04 11:59:49 +03:00
127d817aa1 Guard against calling set_size while applying a configure event more explicitly
Depending on code path taken, geometry.size() == m_pendingSize can
produce incorrect results.

If a configure event is applied, it's fine.

If the window is resized by user, m_pendingSize will have outdated value,
and setWindowGeometry() can ignore future size updates that are valid.

In hindsight, we need special hooks in the QWaylandWindow to request and
apply new geometry. Rather than have one function that deals with all cases.
2024-04-03 15:15:37 +03:00
c407f9a8c4 Update Frameworks version requirement to 6.0.0
GIT_SILENT
2024-02-21 14:33:45 +00:00
17be1332cf Port to asynchronous roundtrip
When a resize is driven client side we wait for the compositor to have a
chance to reconfigure us before submitting the next frame.

Using a blocking round trip caused an issue. Instead block isExposed and
trigger an expose event whilst a sync is in progress.
2024-02-16 11:31:49 +00:00
13 changed files with 268 additions and 73 deletions

View File

@ -7,3 +7,6 @@ Dependencies:
'frameworks/extra-cmake-modules': '@latest-kf6'
'third-party/wayland': '@latest'
'third-party/wayland-protocols': '@latest'
Options:
require-passing-tests-on: ['Linux', 'FreeBSD', 'Windows']

View File

@ -4,13 +4,13 @@
cmake_minimum_required(VERSION 3.16)
project(layershellqt)
set(PROJECT_VERSION "6.0.80")
set(PROJECT_VERSION "6.1.90")
set(PROJECT_VERSION_MAJOR 6)
set(CMAKE_C_STANDARD 99)
set(QT_MIN_VERSION "6.6.0")
set(KF6_MIN_VERSION "5.240.0")
set(QT_MIN_VERSION "6.7.0")
set(KF6_MIN_VERSION "6.5.0")
set(KDE_COMPILERSETTINGS_LEVEL "5.82")
set(CMAKE_CXX_STANDARD 20)
@ -31,7 +31,8 @@ include(GenerateExportHeader)
include(KDEClangFormat)
include(ECMQtDeclareLoggingCategory)
include(ECMQmlModule)
include(KDEGitCommitHooks)
include(ECMGenerateQDoc)
find_package(Qt6 ${QT_MIN_VERSION} CONFIG REQUIRED COMPONENTS WaylandClient Qml)
find_package(WaylandScanner REQUIRED)
@ -53,6 +54,8 @@ ecm_setup_version(${PROJECT_VERSION} VARIABLE_PREFIX LAYERSHELLQT
file(GLOB_RECURSE ALL_CLANG_FORMAT_SOURCE_FILES *.cpp *.h)
kde_clang_format(${ALL_CLANG_FORMAT_SOURCE_FILES})
kde_configure_git_pre_commit_hook(CHECKS CLANG_FORMAT)
ecm_set_disabled_deprecation_versions(QT 6.5
KF 5.240
)

View File

@ -4,10 +4,16 @@
remove_definitions(-DQT_NO_SIGNALS_SLOTS_KEYWORDS)
add_library(LayerShellQtInterface)
qt6_generate_wayland_protocol_client_sources(LayerShellQtInterface FILES
${WaylandProtocols_DATADIR}/stable/xdg-shell/xdg-shell.xml
${WaylandProtocols_DATADIR}/staging/xdg-activation/xdg-activation-v1.xml
${CMAKE_CURRENT_SOURCE_DIR}/wlr-layer-shell-unstable-v1.xml
if (Qt6_VERSION VERSION_GREATER_EQUAL "6.8.0")
set(private_code_option "PRIVATE_CODE")
endif()
qt6_generate_wayland_protocol_client_sources(LayerShellQtInterface
${private_code_option}
FILES
${WaylandProtocols_DATADIR}/stable/xdg-shell/xdg-shell.xml
${WaylandProtocols_DATADIR}/staging/xdg-activation/xdg-activation-v1.xml
${CMAKE_CURRENT_SOURCE_DIR}/wlr-layer-shell-unstable-v1.xml
)
ecm_qt_declare_logging_category(LayerShellQtInterface
@ -62,6 +68,8 @@ generate_export_header(LayerShellQtInterface
EXPORT_FILE_NAME LayerShellQt/layershellqt_export.h
)
ecm_generate_qdoc(LayerShellQtInterface layershellqt.qdocconf)
install(TARGETS layer-shell
LIBRARY DESTINATION ${KDE_INSTALL_QTPLUGINDIR}/wayland-shell-integration)

View File

@ -4,20 +4,47 @@
* SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
*/
#include <QQmlExtensionPlugin>
#include "../interfaces/window.h"
#include <QQmlExtensionPlugin>
#include <qqml.h>
QML_DECLARE_TYPEINFO(LayerShellQt::Window, QML_HAS_ATTACHED_PROPERTIES)
class ExtQMargins
{
QMargins m_margins;
Q_GADGET
Q_PROPERTY(int left READ left WRITE setLeft FINAL)
Q_PROPERTY(int right READ right WRITE setRight FINAL)
Q_PROPERTY(int top READ top WRITE setTop FINAL)
Q_PROPERTY(int bottom READ bottom WRITE setBottom FINAL)
QML_FOREIGN(QMargins)
QML_EXTENDED(ExtQMargins)
public:
ExtQMargins(const QMargins &margins);
int left() const { return m_margins.left(); }
void setLeft(int left) { m_margins.setLeft(left); }
int right() const { return m_margins.right(); }
void setRight(int right) { m_margins.setRight(right); }
int top() const { return m_margins.top(); }
void setTop(int top) { m_margins.setTop(top); }
int bottom() const { return m_margins.bottom(); }
void setBottom(int bottom) { m_margins.setBottom(bottom); }
};
class Plugin : public QQmlExtensionPlugin
{
Q_PLUGIN_METADATA(IID "org.kde.layershellqt")
Q_OBJECT
public:
void registerTypes(const char *uri) override {
void registerTypes(const char *uri) override
{
Q_ASSERT(QLatin1String(uri) == QLatin1String("org.kde.layershell"));
qmlRegisterType<LayerShellQt::Window>(uri, 1, 0, "Window");
qmlRegisterExtendedUncreatableType<QMargins, ExtQMargins>(uri, 1, 0, "ExtQMargins", QStringLiteral("Only created from C++"));
}
};

View File

@ -13,12 +13,18 @@
namespace LayerShellQt
{
/**
* Sets the right environment so the shells created from now on use wlr-layer-shell.
/*!
* \class LayerShellQt::Shell
* \inmodule LayerShellQt
* \inheaderfile LayerShellQt/Shell
*
*/
class LAYERSHELLQT_EXPORT Shell
{
public:
/*!
* Sets the right environment so the shells created from now on use wlr-layer-shell.
*/
static void useLayerShell();
};

View File

@ -18,10 +18,18 @@ namespace LayerShellQt
{
class WindowPrivate;
/*!
* \class LayerShellQt::Window
* \inmodule LayerShellQt
* \inheaderfile LayerShellQt/Window
*
* \brief A window.
*/
class LAYERSHELLQT_EXPORT Window : public QObject
{
Q_OBJECT
Q_PROPERTY(Anchors anchors READ anchors WRITE setAnchors NOTIFY anchorsChanged)
/*! \property LayerShellQt::Window::scope */
Q_PROPERTY(QString scope READ scope WRITE setScope)
Q_PROPERTY(QMargins margins READ margins WRITE setMargins NOTIFY marginsChanged)
Q_PROPERTY(qint32 exclusionZone READ exclusionZone WRITE setExclusiveZone NOTIFY exclusionZoneChanged)
@ -32,17 +40,24 @@ class LAYERSHELLQT_EXPORT Window : public QObject
public:
~Window() override;
/*!
* \value AnchorNone
* \value AnchorTop The top edge of the anchor rectangle
* \value AnchorBottom The bottom edge of the anchor rectangle
* \value AnchorLeft The left edge of the anchor rectangle
* \value AnchorRight The right edge of the anchor rectangle
*/
enum Anchor {
AnchorNone = 0,
AnchorTop = 1, ///< The top edge of the anchor rectangle
AnchorBottom = 2, ///< The bottom edge of the anchor rectangle
AnchorLeft = 4, ///< The left edge of the anchor rectangle
AnchorRight = 8, ///< The right edge of the anchor rectangle
AnchorTop = 1,
AnchorBottom = 2,
AnchorLeft = 4,
AnchorRight = 8,
};
Q_ENUM(Anchor);
Q_DECLARE_FLAGS(Anchors, Anchor)
/**
/*!
* This enum type is used to specify the layer where a surface can be put in.
*/
enum Layer {
@ -53,7 +68,7 @@ public:
};
Q_ENUM(Layer)
/**
/*!
* This enum type is used to specify how the layer surface handles keyboard focus.
*/
enum KeyboardInteractivity {
@ -63,7 +78,7 @@ public:
};
Q_ENUM(KeyboardInteractivity)
/**
/*!
* This enum type is used to specify which screen to place the surface on.
* ScreenFromQWindow (the default) reads QWindow::screen() while ScreenFromCompositor
* passes nil and lets the compositor decide.
@ -95,7 +110,7 @@ public:
void setScreenConfiguration(ScreenConfiguration screenConfiguration);
ScreenConfiguration screenConfiguration() const;
/**
/*!
* Sets a string based identifier for this window.
* This may be used by a compositor to determine stacking
* order within a given layer.
@ -103,9 +118,17 @@ public:
* May also be referred to as a role
*/
void setScope(const QString &scope);
/*!
* A string based identifier for this window.
* This may be used by a compositor to determine stacking
* order within a given layer.
*
* May also be referred to as a role
*/
QString scope() const;
/**
/*!
* Whether the QWindow should be closed when the layer surface is dismissed by the compositor.
* For example, if the associated screen has been removed.
*
@ -114,7 +137,7 @@ public:
void setCloseOnDismissed(bool close);
bool closeOnDismissed() const;
/**
/*!
* Gets the LayerShell Window for a given Qt Window
* Ownership is not transferred
*/

72
src/layershellqt.qdoc Normal file
View File

@ -0,0 +1,72 @@
/*!
\module LayerShellQt
\title LayerShellQt C++ Classes
\cmakepackage LayerShellQt
\cmaketargetitem LayerShellQt::Interface
*/
/*!
\namespace LayerShellQt
\inmodule LayerShellQt
Stuff
*/
/*!
\qmlmodule org.kde.layershell
\title LayerShellQt QML Types
*/
/*!
\qmltype Window
\inqmlmodule org.kde.layershell
\nativetype LayerShellQt::Window
*/
/*!
\qmlproperty string Window::scope
A scope.
*/
/*!
\qmlproperty int Window::margins.top
\qmlproperty int Window::margins.bottom
\qmlproperty int Window::margins.left
\qmlproperty int Window::margins.right
Margins
*/
/*!
\qmlproperty int Window::exclusionZone
*/
/*!
\page layershellqt-index.html
\title LayerShellQt
Layer Shell
\section1 Using the Module
\include {module-use.qdocinc} {using the c++ api}
\section2 Building with CMake
\include {module-use.qdocinc} {building with cmake} {Widgets}
\section1 API Reference
\list
\li \l{LayerShellQt C++ Classes}
\li \l{LayerShellQt QML Types}
\endlist
\section1 Licenses
TODO
*/

30
src/layershellqt.qdocconf Normal file
View File

@ -0,0 +1,30 @@
include($KDE_DOCS/global/qt-module-defaults.qdocconf)
project = LayerShellQt
description = Qt component to allow applications to make use of the Wayland wl-layer-shell protocol
documentationinheaders = true
headerdirs = .
headers.fileextensions = "*.qdoc *.h"
outputdir = doc/html
outputformats = HTML
depends += \
qtcore
qhp.projects = LayerShellQt
qhp.LayerShellQt.file = layershellqt.qhp
qhp.LayerShellQt.namespace = org.kde.layershellqt.$QT_VERSION_TAG
qhp.LayerShellQt.virtualFolder = layershellqt
qhp.LayerShellQt.indexTitle = LayerShellQt
qhp.LayerShellQt.indexRoot =
qhp.LayerShellQt.subprojects = classes
qhp.LayerShellQt.subprojects.classes.title = C++ Classes
qhp.LayerShellQt.subprojects.classes.indexTitle = LayerShellQt C++ Classes
qhp.LayerShellQt.subprojects.classes.selectors = class fake:headerfile
qhp.LayerShellQt.subprojects.classes.sortPages = true

View File

@ -32,5 +32,4 @@ QtWaylandClient::QWaylandShellSurface *QWaylandLayerShellIntegration::createShel
return new QWaylandLayerSurface(this, window);
}
}

View File

@ -18,14 +18,19 @@ class QWaylandXdgActivationV1;
namespace LayerShellQt
{
class LAYERSHELLQT_EXPORT QWaylandLayerShellIntegration : public QtWaylandClient::QWaylandShellIntegrationTemplate<QWaylandLayerShellIntegration>, public QtWayland::zwlr_layer_shell_v1
class LAYERSHELLQT_EXPORT QWaylandLayerShellIntegration : public QtWaylandClient::QWaylandShellIntegrationTemplate<QWaylandLayerShellIntegration>,
public QtWayland::zwlr_layer_shell_v1
{
public:
QWaylandLayerShellIntegration();
~QWaylandLayerShellIntegration() override;
QWaylandXdgActivationV1 *activation() const { return m_xdgActivation.data(); }
QWaylandXdgActivationV1 *activation() const
{
return m_xdgActivation.data();
}
QtWaylandClient::QWaylandShellSurface *createShellSurface(QtWaylandClient::QWaylandWindow *window) override;
private:
QScopedPointer<QWaylandXdgActivationV1> m_xdgActivation;
};

View File

@ -41,10 +41,12 @@ QWaylandLayerSurface::QWaylandLayerSurface(QWaylandLayerShellIntegration *shell,
setLayer(m_interface->layer());
});
set_anchor(m_interface->anchors());
setAnchor(m_interface->anchors());
connect(m_interface, &Window::anchorsChanged, this, [this]() {
set_anchor(m_interface->anchors());
setAnchor(m_interface->anchors());
setDesiredSize(m_window->windowContentGeometry().size());
});
setExclusiveZone(m_interface->exclusionZone());
connect(m_interface, &Window::exclusionZoneChanged, this, [this]() {
setExclusiveZone(m_interface->exclusionZone());
@ -64,17 +66,7 @@ QWaylandLayerSurface::QWaylandLayerSurface(QWaylandLayerShellIntegration *shell,
setKeyboardInteractivity(m_interface->keyboardInteractivity());
});
QSize size = window->surfaceSize();
const Window::Anchors anchors = m_interface->anchors();
if ((anchors & Window::AnchorLeft) && (anchors & Window::AnchorRight)) {
size.setWidth(0);
}
if ((anchors & Window::AnchorTop) && (anchors & Window::AnchorBottom)) {
size.setHeight(0);
}
if (size.isValid() && size != QSize(0, 0)) {
set_size(size.width(), size.height());
}
setDesiredSize(window->windowContentGeometry().size());
}
QWaylandLayerSurface::~QWaylandLayerSurface()
@ -96,12 +88,8 @@ void QWaylandLayerSurface::zwlr_layer_surface_v1_configure(uint32_t serial, uint
if (!m_configured) {
m_configured = true;
window()->resizeFromApplyConfigure(m_pendingSize);
#if QT_VERSION < QT_VERSION_CHECK(6, 7, 0)
window()->handleExpose(QRect(QPoint(), m_pendingSize));
#else
window()->sendRecursiveExposeEvent();
#endif
applyConfigure();
sendExpose();
} else {
// Later configures are resizes, so we have to queue them up for a time when we
// are not painting to the window.
@ -122,13 +110,33 @@ void QWaylandLayerSurface::attachPopup(QtWaylandClient::QWaylandShellSurface *po
void QWaylandLayerSurface::applyConfigure()
{
#if QT_VERSION < QT_VERSION_CHECK(6, 9, 0)
m_configuring = true;
#endif
window()->resizeFromApplyConfigure(m_pendingSize);
#if QT_VERSION < QT_VERSION_CHECK(6, 9, 0)
m_configuring = false;
#endif
}
void QWaylandLayerSurface::setDesiredSize(const QSize &size)
{
const bool horizontallyConstrained = m_interface->anchors().testFlags({Window::AnchorLeft, Window::AnchorRight});
const bool verticallyConstrained = m_interface->anchors().testFlags({Window::AnchorTop, Window::AnchorBottom});
QSize effectiveSize = size;
if (horizontallyConstrained) {
effectiveSize.setWidth(0);
}
if (verticallyConstrained) {
effectiveSize.setHeight(0);
}
set_size(effectiveSize.width(), effectiveSize.height());
}
void QWaylandLayerSurface::setAnchor(uint anchor)
{
set_anchor(anchor);
setWindowGeometry(window()->windowContentGeometry());
}
void QWaylandLayerSurface::setExclusiveZone(int32_t zone)
@ -159,26 +167,21 @@ void QWaylandLayerSurface::setLayer(uint32_t layer)
set_layer(layer);
}
#if QT_VERSION < QT_VERSION_CHECK(6, 9, 0)
void QWaylandLayerSurface::setWindowGeometry(const QRect &geometry)
{
// if we are setting it to the last size we were configured at, we don't need to do anything
if (geometry.size() == m_pendingSize) {
if (m_configuring) {
return;
}
const bool horizontallyConstrained = m_interface->anchors().testFlags({Window::AnchorLeft, Window::AnchorRight});
const bool verticallyConstrained = m_interface->anchors().testFlags({Window::AnchorTop, Window::AnchorBottom});
QSize size = geometry.size();
if (horizontallyConstrained) {
size.setWidth(0);
}
if (verticallyConstrained) {
size.setHeight(0);
}
set_size(size.width(), size.height());
wl_display_roundtrip(m_window->display()->wl_display());
setDesiredSize(geometry.size());
}
#else
void QWaylandLayerSurface::setWindowSize(const QSize &size)
{
setDesiredSize(size);
}
#endif
bool QWaylandLayerSurface::requestActivate()
{
@ -192,14 +195,12 @@ bool QWaylandLayerSurface::requestActivate()
return true;
} else {
const auto focusWindow = QGuiApplication::focusWindow();
const auto wlWindow = focusWindow ? static_cast<QtWaylandClient::QWaylandWindow*>(focusWindow->handle()) : window();
const auto wlWindow = focusWindow ? static_cast<QtWaylandClient::QWaylandWindow *>(focusWindow->handle()) : window();
if (const auto seat = wlWindow->display()->lastInputDevice()) {
const auto tokenProvider = activation->requestXdgActivationToken(
wlWindow->display(), wlWindow->wlSurface(), 0, QString());
connect(tokenProvider, &QWaylandXdgActivationTokenV1::done, this,
[this](const QString &token) {
m_shell->activation()->activate(token, window()->wlSurface());
});
const auto tokenProvider = activation->requestXdgActivationToken(wlWindow->display(), wlWindow->wlSurface(), 0, QString());
connect(tokenProvider, &QWaylandXdgActivationTokenV1::done, this, [this](const QString &token) {
m_shell->activation()->activate(token, window()->wlSurface());
});
connect(tokenProvider, &QWaylandXdgActivationTokenV1::done, tokenProvider, &QObject::deleteLater);
return true;
}
@ -218,16 +219,23 @@ void QWaylandLayerSurface::requestXdgActivationToken(quint32 serial)
if (!activation->isActive()) {
return;
}
auto tokenProvider = activation->requestXdgActivationToken(
window()->display(), window()->wlSurface(), serial, QString());
auto tokenProvider = activation->requestXdgActivationToken(window()->display(), window()->wlSurface(), serial, QString());
connect(tokenProvider, &QWaylandXdgActivationTokenV1::done, this,
[this](const QString &token) {
Q_EMIT window()->xdgActivationTokenCreated(token);
});
connect(tokenProvider, &QWaylandXdgActivationTokenV1::done, this, [this](const QString &token) {
Q_EMIT window()->xdgActivationTokenCreated(token);
});
connect(tokenProvider, &QWaylandXdgActivationTokenV1::done, tokenProvider, &QObject::deleteLater);
}
void QWaylandLayerSurface::sendExpose()
{
#if QT_VERSION < QT_VERSION_CHECK(6, 7, 0)
window()->handleExpose(QRect(QPoint(), m_pendingSize));
#elif QT_VERSION < QT_VERSION_CHECK(6, 9, 0)
window()->sendRecursiveExposeEvent();
#else
window()->updateExposure();
#endif
}
}

View File

@ -34,6 +34,7 @@ public:
}
void attachPopup(QtWaylandClient::QWaylandShellSurface *popup) override;
void setDesiredSize(const QSize &size);
void setAnchor(uint32_t anchor);
void setExclusiveZone(int32_t zone);
void setExclusiveEdge(uint32_t edge);
@ -42,13 +43,18 @@ public:
void setLayer(uint32_t layer);
void applyConfigure() override;
#if QT_VERSION < QT_VERSION_CHECK(6, 9, 0)
void setWindowGeometry(const QRect &geometry) override;
#else
void setWindowSize(const QSize &size) override;
#endif
bool requestActivate() override;
void setXdgActivationToken(const QString &token) override;
void requestXdgActivationToken(quint32 serial) override;
private:
void sendExpose();
void zwlr_layer_surface_v1_configure(uint32_t serial, uint32_t width, uint32_t height) override;
void zwlr_layer_surface_v1_closed() override;
@ -57,7 +63,11 @@ private:
QtWaylandClient::QWaylandWindow *m_window;
QSize m_pendingSize;
QString m_activationToken;
bool m_configured = false;
#if QT_VERSION < QT_VERSION_CHECK(6, 9, 0)
bool m_configuring = false;
#endif
};
}

View File

@ -39,6 +39,7 @@ Item
LayerShell.Window.anchors: LayerShell.Window.AnchorLeft
LayerShell.Window.layer: LayerShell.Window.LayerTop
LayerShell.Window.exclusionZone: width
LayerShell.Window.margins.left: 100
width: 100
height: 100