diff --git a/CMakeLists.txt b/CMakeLists.txt index b6ddd91b..e2337fed 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -97,6 +97,7 @@ set(SRC_FILES src/Register.cc src/RegisterPage.cc src/SlidingStackWidget.cc + src/Splitter.cc src/Sync.cc src/TextInputWidget.cc src/TopRoomBar.cc @@ -171,6 +172,7 @@ qt5_wrap_cpp(MOC_HEADERS include/RegisterPage.h include/RoomInfoListItem.h include/RoomList.h + include/Splitter.h include/UserInfoWidget.h include/SlidingStackWidget.h include/TopRoomBar.h diff --git a/include/RoomInfoListItem.h b/include/RoomInfoListItem.h index df7f11e8..f01fddf4 100644 --- a/include/RoomInfoListItem.h +++ b/include/RoomInfoListItem.h @@ -17,13 +17,8 @@ #pragma once -#include -#include -#include -#include +#include -#include "Avatar.h" -#include "Badge.h" #include "RippleOverlay.h" #include "RoomState.h" @@ -52,46 +47,37 @@ public slots: protected: void mousePressEvent(QMouseEvent *event) override; + void paintEvent(QPaintEvent *event) override; private: - void setElidedText(QLabel *label, QString text, int width); + const int Padding = 10; + const int IconSize = 45; RippleOverlay *ripple_overlay_; RoomState state_; - QString room_id_; - QHBoxLayout *topLayout_; + QString roomId_; + QString roomName_; + QString lastMessage_; + QString lastTimestamp_; - QVBoxLayout *avatarLayout_; - QVBoxLayout *textLayout_; + QPixmap roomAvatar_; - QWidget *avatarWidget_; - QWidget *textWidget_; + bool isPressed_; - QLabel *roomName_; - QLabel *roomTopic_; - - Avatar *roomAvatar_; - Badge *unreadMessagesBadge_; - - QString pressed_style_; - QString normal_style_; - - bool is_pressed_; - - int max_height_; - int unread_msg_count_; + int maxHeight_ = 60; + int unreadMsgCount_ = 0; }; inline int RoomInfoListItem::unreadMessageCount() const { - return unread_msg_count_; + return unreadMsgCount_; } inline bool RoomInfoListItem::isPressed() const { - return is_pressed_; + return isPressed_; } inline RoomState RoomInfoListItem::state() const @@ -99,7 +85,7 @@ inline RoomState RoomInfoListItem::state() const return state_; } -inline void RoomInfoListItem::setAvatar(const QImage &avatar_image) +inline void RoomInfoListItem::setAvatar(const QImage &img) { - roomAvatar_->setImage(avatar_image); + roomAvatar_ = QPixmap::fromImage(img.scaled(IconSize, IconSize, Qt::KeepAspectRatio, Qt::SmoothTransformation)); } diff --git a/include/Splitter.h b/include/Splitter.h new file mode 100644 index 00000000..d9610730 --- /dev/null +++ b/include/Splitter.h @@ -0,0 +1,38 @@ +/* + * nheko Copyright (C) 2017 Konstantinos Sideris + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#pragma once + +#include + +class Splitter : public QSplitter +{ + Q_OBJECT +public: + explicit Splitter(int first_step, int second_step, QWidget *parent = nullptr); + +private: + void onSplitterMoved(int pos, int index); + + int firstStep_ = 60; + int secondStep_ = 300; + + int moveEventLimit_ = 50; + + int leftMoveCount_ = 0; + int rightMoveCount_ = 0; +}; diff --git a/src/RoomInfoListItem.cc b/src/RoomInfoListItem.cc index f3248ee3..1ed1a906 100644 --- a/src/RoomInfoListItem.cc +++ b/src/RoomInfoListItem.cc @@ -17,6 +17,7 @@ #include #include +#include #include "Ripple.h" #include "RoomInfoListItem.h" @@ -25,133 +26,150 @@ RoomInfoListItem::RoomInfoListItem(RoomState state, QString room_id, QWidget *parent) : QWidget(parent) , state_(state) - , room_id_(room_id) - , is_pressed_(false) - , max_height_(60) - , unread_msg_count_(0) + , roomId_(room_id) + , isPressed_(false) + , maxHeight_(60) + , unreadMsgCount_(0) { - normal_style_ = - "QWidget { color: black; background-color: #f8fbfe}" - "QLabel { border: none; }"; - - pressed_style_ = - "QWidget { background-color: #acc7dc; color: black;}" - "QLabel { border: none; }"; - - setStyleSheet(normal_style_); setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); - setAutoFillBackground(true); - setMaximumSize(parent->width(), max_height_); - - QString room_name = state_.resolveName(); - QString room_topic = state_.topic.content().topic().simplified(); - - topLayout_ = new QHBoxLayout(this); - topLayout_->setSpacing(0); - topLayout_->setMargin(0); - - avatarWidget_ = new QWidget(this); - avatarWidget_->setMaximumSize(max_height_, max_height_); - textWidget_ = new QWidget(this); - - avatarLayout_ = new QVBoxLayout(avatarWidget_); - avatarLayout_->setSpacing(0); - avatarLayout_->setContentsMargins(0, 5, 0, 5); - - textLayout_ = new QVBoxLayout(textWidget_); - textLayout_->setSpacing(0); - textLayout_->setContentsMargins(0, 5, 0, 5); - - roomAvatar_ = new Avatar(avatarWidget_); - roomAvatar_->setLetter(QChar(room_name[0])); - roomAvatar_->setSize(max_height_ - 20); - roomAvatar_->setTextColor("#555459"); - roomAvatar_->setBackgroundColor("#d6dde3"); - - unreadMessagesBadge_ = new Badge(roomAvatar_); - unreadMessagesBadge_->setRelativePosition(12, 10); - unreadMessagesBadge_->setDiameter(5); - unreadMessagesBadge_->setBackgroundColor("#f8fbfe"); - unreadMessagesBadge_->setTextColor("black"); - - // TODO: Initialize when nheko can restore messages from previous session. - unreadMessagesBadge_->hide(); - - avatarLayout_->addWidget(roomAvatar_); - - roomName_ = new QLabel(room_name, textWidget_); - roomName_->setMaximumSize(parent->width() - max_height_, 20); - roomName_->setFont(QFont("Open Sans", 11)); - roomName_->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); - - roomTopic_ = new QLabel(room_topic, textWidget_); - roomTopic_->setMaximumSize(parent->width() - max_height_, 20); - roomTopic_->setFont(QFont("Open Sans", 10)); - roomTopic_->setStyleSheet("color: #171919"); - roomTopic_->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); - - textLayout_->addWidget(roomName_); - textLayout_->addWidget(roomTopic_); - - topLayout_->addWidget(avatarWidget_); - topLayout_->addWidget(textWidget_); - - setElidedText(roomName_, room_name, parent->width() - max_height_); - setElidedText(roomTopic_, room_topic, parent->width() - max_height_); + setFixedHeight(maxHeight_); + setMaximumSize(parent->width(), maxHeight_); QPainterPath path; - path.addRect(0, 0, parent->width(), max_height_); + path.addRect(0, 0, parent->width(), height()); ripple_overlay_ = new RippleOverlay(this); ripple_overlay_->setClipPath(path); ripple_overlay_->setClipping(true); +} - setLayout(topLayout_); +void RoomInfoListItem::paintEvent(QPaintEvent *event) +{ + Q_UNUSED(event); + + QPainter p(this); + p.setRenderHint(QPainter::TextAntialiasing); + p.setRenderHint(QPainter::SmoothPixmapTransform); + p.setRenderHint(QPainter::Antialiasing); + + if (isPressed_) + p.fillRect(rect(), QColor("#38A3D8")); + else + p.fillRect(rect(), QColor("#F8FBFE")); + + QFont font("Open Sans", 10); + + QFontMetrics metrics(font); + p.setFont(font); + + QRect avatarRegion(Padding, Padding, IconSize, IconSize); + + if (isPressed_) { + QPen pen(QColor("white")); + p.setPen(pen); + } + + auto name = metrics.elidedText(state_.resolveName(), Qt::ElideRight, (width() - IconSize - 2 * Padding) * 0.8); + p.drawText(QPoint(2 * Padding + IconSize, avatarRegion.center().y() - metrics.height() / 2), name); + + if (!isPressed_) { + QPen pen(QColor("#5d6565")); + p.setPen(pen); + } + + int bottom_y = avatarRegion.center().y() + metrics.height() / 2 + Padding / 2; + double descPercentage = 0.95; + + if (unreadMsgCount_ > 0) + descPercentage = 0.8; + + auto description = metrics.elidedText(state_.resolveTopic(), Qt::ElideRight, width() * descPercentage - 2 * Padding - IconSize); + p.drawText(QPoint(2 * Padding + IconSize, bottom_y), description); + + p.setPen(Qt::NoPen); + + if (unreadMsgCount_ > 0) { + QBrush brush; + brush.setStyle(Qt::SolidPattern); + brush.setColor(QColor("#38A3D8")); + + p.setBrush(brush); + p.setPen(Qt::NoPen); + + QFont msgFont("Open Sans", 8); + msgFont.setStyleName("Bold"); + + p.setFont(msgFont); + + int diameter = 20; + + QRectF r(width() - diameter - Padding, bottom_y - diameter / 2 - 5, diameter, diameter); + + p.setPen(Qt::NoPen); + p.drawEllipse(r); + + p.setPen(QPen(QColor("white"))); + p.setBrush(Qt::NoBrush); + p.drawText(r.translated(0, -0.5), Qt::AlignCenter, QString::number(unreadMsgCount_)); + } + + // We using the first letter of room's name. + if (roomAvatar_.isNull()) { + QBrush brush; + brush.setStyle(Qt::SolidPattern); + brush.setColor("#eee"); + + p.setPen(Qt::NoPen); + p.setBrush(brush); + + p.drawEllipse(avatarRegion.center(), IconSize / 2, IconSize / 2); + + font.setPixelSize(13); + p.setFont(font); + p.setPen(QColor("#333")); + p.setBrush(Qt::NoBrush); + p.drawText(avatarRegion.translated(0, -1), Qt::AlignCenter, QChar(state_.resolveName()[0])); + } else { + QPainterPath path; + path.addEllipse(Padding, Padding, IconSize, IconSize); + p.setClipPath(path); + p.drawPixmap(avatarRegion, roomAvatar_); + } } void RoomInfoListItem::updateUnreadMessageCount(int count) { - unread_msg_count_ += count; - unreadMessagesBadge_->setText(QString::number(unread_msg_count_)); - unreadMessagesBadge_->show(); + unreadMsgCount_ += count; + repaint(); } void RoomInfoListItem::clearUnreadMessageCount() { - unread_msg_count_ = 0; - unreadMessagesBadge_->setText(""); - unreadMessagesBadge_->hide(); + unreadMsgCount_ = 0; + repaint(); } void RoomInfoListItem::setPressedState(bool state) { - if (!is_pressed_ && state) { - is_pressed_ = state; - setStyleSheet(pressed_style_); - } else if (is_pressed_ && !state) { - is_pressed_ = state; - setStyleSheet(normal_style_); + if (!isPressed_ && state) { + isPressed_ = state; + update(); + } else if (isPressed_ && !state) { + isPressed_ = state; + update(); } } void RoomInfoListItem::setState(const RoomState &new_state) { - if (state_.resolveName() != new_state.resolveName()) - setElidedText(roomName_, new_state.resolveName(), parentWidget()->width() - max_height_); - - if (state_.resolveTopic() != new_state.resolveTopic()) - setElidedText(roomTopic_, new_state.resolveTopic(), parentWidget()->width() - max_height_); - - if (new_state.avatar.content().url().toString().isEmpty()) - roomAvatar_->setLetter(QChar(new_state.resolveName()[0])); - state_ = new_state; + repaint(); } void RoomInfoListItem::mousePressEvent(QMouseEvent *event) { - emit clicked(room_id_); + emit clicked(roomId_); setPressedState(true); @@ -163,20 +181,13 @@ void RoomInfoListItem::mousePressEvent(QMouseEvent *event) ripple->setRadiusEndValue(radiusEndValue); ripple->setOpacityStartValue(0.15); - ripple->setColor(QColor("#052B49")); - ripple->radiusAnimation()->setDuration(300); - ripple->opacityAnimation()->setDuration(500); + ripple->setColor(QColor("white")); + ripple->radiusAnimation()->setDuration(200); + ripple->opacityAnimation()->setDuration(400); ripple_overlay_->addRipple(ripple); } -void RoomInfoListItem::setElidedText(QLabel *label, QString text, int width) -{ - QFontMetrics metrics(label->font()); - QString elidedText = metrics.elidedText(text, Qt::ElideRight, width); - label->setText(elidedText); -} - RoomInfoListItem::~RoomInfoListItem() { } diff --git a/src/Splitter.cc b/src/Splitter.cc new file mode 100644 index 00000000..0a3ea416 --- /dev/null +++ b/src/Splitter.cc @@ -0,0 +1,84 @@ +/* + * nheko Copyright (C) 2017 Konstantinos Sideris + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include + +#include "Splitter.h" + +Splitter::Splitter(int first_step, int second_step, QWidget *parent) + : QSplitter(parent) + , firstStep_{first_step} + , secondStep_{second_step} +{ + connect(this, &QSplitter::splitterMoved, this, &Splitter::onSplitterMoved); +} + +void Splitter::onSplitterMoved(int pos, int index) +{ + Q_UNUSED(pos); + Q_UNUSED(index); + + auto s = sizes(); + + if (s.count() < 2) { + qWarning() << "Splitter needs at least two children"; + return; + } + + if (s[0] == secondStep_) { + rightMoveCount_ += 1; + + if (rightMoveCount_ > moveEventLimit_) { + auto left = widget(0); + auto pos = left->mapFromGlobal(QCursor::pos()); + + // if we are coming from the right, the cursor should + // end up on the first widget. + if (left->rect().contains(pos)) { + qDebug() << "Resizing left"; + + left->setMinimumWidth(firstStep_); + left->setMaximumWidth(firstStep_); + + rightMoveCount_ = 0; + } + } + } else if (s[0] == firstStep_) { + leftMoveCount_ += 1; + + if (leftMoveCount_ > moveEventLimit_) { + auto left = widget(0); + auto right = widget(1); + auto pos = right->mapFromGlobal(QCursor::pos()); + + // We move the start a little further so the transition isn't so abrupt. + auto extended = right->rect(); + extended.translate(100, 0); + + // if we are coming from the left, the cursor should + // end up on the second widget. + if (extended.contains(pos)) { + qDebug() << "Resizing Right"; + + left->setMinimumWidth(secondStep_); + left->setMaximumWidth(2 * secondStep_); + + leftMoveCount_ = 0; + } + } + } +}