aboutsummaryrefslogtreecommitdiff
path: root/qtmips_gui
diff options
context:
space:
mode:
authorKarel Kočí <cynerd@email.cz>2017-12-15 22:45:28 +0100
committerKarel Kočí <cynerd@email.cz>2017-12-15 22:45:28 +0100
commite6ca4b4568e311b47239bfe83de15ed9e91c57b9 (patch)
tree3da2f72faf360058bae02c35b0c724233bd64d61 /qtmips_gui
parentbbea996112eb7ac81ec50d2af08f4bd681d0e50d (diff)
downloadqtmips-e6ca4b4568e311b47239bfe83de15ed9e91c57b9.tar.gz
qtmips-e6ca4b4568e311b47239bfe83de15ed9e91c57b9.tar.bz2
qtmips-e6ca4b4568e311b47239bfe83de15ed9e91c57b9.zip
Implement few initial graphic elements
Diffstat (limited to 'qtmips_gui')
-rw-r--r--qtmips_gui/coreview.cpp48
-rw-r--r--qtmips_gui/coreview.h46
-rw-r--r--qtmips_gui/coreview/connection.cpp79
-rw-r--r--qtmips_gui/coreview/connection.h60
-rw-r--r--qtmips_gui/coreview/latch.cpp51
-rw-r--r--qtmips_gui/coreview/latch.h45
-rw-r--r--qtmips_gui/coreview/multiplexer.cpp79
-rw-r--r--qtmips_gui/coreview/multiplexer.h41
-rw-r--r--qtmips_gui/coreview/programcounter.cpp48
-rw-r--r--qtmips_gui/coreview/programcounter.h42
-rw-r--r--qtmips_gui/mainwindow.cpp26
-rw-r--r--qtmips_gui/qtmips_gui.pro12
12 files changed, 515 insertions, 62 deletions
diff --git a/qtmips_gui/coreview.cpp b/qtmips_gui/coreview.cpp
index 4d23bd8..660f78d 100644
--- a/qtmips_gui/coreview.cpp
+++ b/qtmips_gui/coreview.cpp
@@ -1,35 +1,35 @@
#include "coreview.h"
-CoreView::CoreView(QWidget *parent) : QGraphicsView(parent) {
+CoreView::CoreView(QWidget *parent, QtMipsMachine *machine) : QGraphicsView(parent) {
+ setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
+ setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
+ setRenderHints(QPainter::Antialiasing | QPainter::SmoothPixmapTransform);
-}
-
-/*
-CoreViewBlock::CoreViewBlock() {
-}
-
-CoreViewLine::CoreViewLine() {
-
-}
-
-CoreViewLine::CoreViewLine(struct point start, struct point end, QList<struct point> axis) {
+ this->machine = machine;
+ // Identification cross
+ scene.addLine(400, 0, 400, 800);
+ scene.addLine(0, 400, 800, 400);
-}
+ pc = new coreview::ProgramCounter(machine);
+ pc_multiplexer = new coreview::Multiplexer(4);
+ testlatch = new coreview::Latch(machine, 300);
-CoreViewLine::~CoreViewLine() {
+ pc2pc = new coreview::Connection(pc_multiplexer->connector_out(), pc->connector_in());
-}
+ scene.addItem(pc);
+ scene.addItem(pc_multiplexer);
+ scene.addItem(testlatch);
+ scene.addItem(pc2pc);
-void CoreViewLine::set_start(struct point p) {
+ pc->setPos(100,100);
+ pc_multiplexer->setPos(60, 100);
+ pc_multiplexer->set(2);
+ setScene(&scene);
+ // TODO fitInView doesn't work as I want so reimplement or do something with it
+ //fitInView(0, 0, 201, 201, Qt::KeepAspectRatioByExpanding);
}
-void CoreViewLine::set_end(struct point p) {
-
+void CoreView::resizeEvent(QResizeEvent *event) {
+ // fitInView(0, 0, 201, 201, Qt::KeepAspectRatioByExpanding);
}
-
-void CoreViewLine::set_axis(QList<struct point> axs) {
-
-}
-
-*/
diff --git a/qtmips_gui/coreview.h b/qtmips_gui/coreview.h
index a2772fb..fc69861 100644
--- a/qtmips_gui/coreview.h
+++ b/qtmips_gui/coreview.h
@@ -2,45 +2,33 @@
#define COREVIEW_H
#include <QGraphicsView>
-#include <QGraphicsItem>
-#include <QList>
-#include "machineconfig.h"
+#include <QGraphicsScene>
+#include "qtmipsmachine.h"
+#include "coreview/connection.h"
+#include "coreview/programcounter.h"
+#include "coreview/multiplexer.h"
+#include "coreview/latch.h"
class CoreView : public QGraphicsView {
Q_OBJECT
public:
- CoreView(QWidget *parent);
+ CoreView(QWidget *parent, QtMipsMachine *machine);
private:
+ void resizeEvent(QResizeEvent *event);
-};
+ QGraphicsScene scene;
+ QtMipsMachine *machine;
-/*
-class CoreViewBlock : public QGraphicsItem {
- Q_OBJECT
-public:
- CoreViewBlock();
+ coreview::ProgramCounter *pc;
+ coreview::Multiplexer *pc_multiplexer;
+ coreview::Connection *pc2pc;
+ coreview::Latch *testlatch;
};
-class CoreViewLine : public QGraphicsItem {
- Q_OBJECT
-public:
- struct point {
- int x1, y1, x2, y2;
- };
-
- CoreViewLine();
- CoreViewLine(struct point start, struct point end, QList<struct point> axis);
- ~CoreViewLine();
+#else
- void set_start(struct point);
- void set_end(struct point);
- void set_axis(QList<struct point>);
-
-protected:
- struct point start, end;
- QList<struct point> axis;
-};
-*/
+class CoreView;
+class CoreViewBlock;
#endif // COREVIEW_H
diff --git a/qtmips_gui/coreview/connection.cpp b/qtmips_gui/coreview/connection.cpp
new file mode 100644
index 0000000..ebca519
--- /dev/null
+++ b/qtmips_gui/coreview/connection.cpp
@@ -0,0 +1,79 @@
+#include "connection.h"
+
+using namespace coreview;
+
+void Connector::setPos(qreal x, qreal y) {
+ qx = x;
+ qy = y;
+ emit updated();
+}
+
+qreal Connector::x() const {
+ return qx;
+}
+
+qreal Connector::y() const {
+ return qy;
+}
+
+QPointF Connector::point() const {
+ return QPointF(qx, qy);
+}
+
+Connection::Connection(const Connector *a, const Connector *b) : QGraphicsObject(nullptr) {
+ connect(a, SIGNAL(updated()), this, SLOT(moved()));
+ connect(b, SIGNAL(updated()), this, SLOT(moved()));
+ this->a = a;
+ this->b = b;
+ update_pos();
+}
+
+void Connection::setHasText(bool has) {
+ if (has && value == nullptr) {
+ value = new QGraphicsSimpleTextItem(this);
+ value->setText(text);
+ } else if (!has && value != nullptr) {
+ delete value;
+ }
+ update_pos();
+}
+
+void Connection::setText(QString val) {
+ text = val;
+ if (value != nullptr)
+ value->setText(val);
+}
+
+void Connection::moved() {
+ update_pos();
+}
+
+QRectF Connection::boundingRect() const {
+ QRectF rect;
+ for (int i = 0; i < (points.size() - 1); i++) {
+ qreal x = points[i].x();
+ if (x > points[i+1].x())
+ x = points[i+1].x();
+ qreal y = points[i].y();
+ if (y > points[i+1].y())
+ y = points[i+1].y();
+ // TODO pen width
+ rect.united(QRectF(x - 0.5, y - 0.5, fabs(points[i].x() - points[i+1].x()) + 1, fabs(points[i].y() - points[i+1].y()) + 1));
+ }
+ //return rect;
+ return QRectF(0, 0, 300, 300);
+}
+
+void Connection::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) {
+ for (int i = 0; i < (points.size() - 1); i++)
+ painter->drawLine(points[i], points[i+1]);
+ // TODO meaby use QPath instead?
+}
+
+void Connection::update_pos() {
+ points.clear();
+ points.append(a->point());
+ points.append(b->point());
+ // TODO more than one line
+ // TODO update position of value
+}
diff --git a/qtmips_gui/coreview/connection.h b/qtmips_gui/coreview/connection.h
new file mode 100644
index 0000000..eda6afe
--- /dev/null
+++ b/qtmips_gui/coreview/connection.h
@@ -0,0 +1,60 @@
+#ifndef CONNECTION_H
+#define CONNECTION_H
+
+#include <QGraphicsObject>
+#include <QList>
+#include <cmath>
+#include "../coreview.h"
+
+namespace coreview {
+
+class Connector : public QObject {
+ Q_OBJECT
+public:
+ void setPos(qreal x, qreal y);
+ qreal x() const;
+ qreal y() const;
+ QPointF point() const;
+
+signals:
+ void updated();
+
+private:
+ qreal qx, qy;
+};
+
+class Connection : public QGraphicsObject {
+ Q_OBJECT
+public:
+ Connection(const Connector *a, const Connector *b);
+
+ void setHasText(bool has);
+ void setText(QString val);
+
+private slots:
+ void moved();
+
+private:
+ QGraphicsSimpleTextItem *value;
+ QList<QPointF> points;
+ const Connector *a, *b;
+ QString text;
+
+ QRectF boundingRect() const;
+ void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget);
+
+ // TODO line width and possibly bus width
+
+ void update_pos();
+};
+
+}
+
+#else
+
+namespace coreview {
+ class Connector;
+ class Connection;
+};
+
+#endif // CONNECTION_H
diff --git a/qtmips_gui/coreview/latch.cpp b/qtmips_gui/coreview/latch.cpp
new file mode 100644
index 0000000..77bb317
--- /dev/null
+++ b/qtmips_gui/coreview/latch.cpp
@@ -0,0 +1,51 @@
+#include "latch.h"
+
+using namespace coreview;
+
+//////////////////////
+#define WIDTH 10
+#define PENW 1
+//////////////////////
+
+Latch::Latch(QtMipsMachine *machine, qreal height) {
+ this->height = height;
+ connect(machine, SIGNAL(tick()), this, SLOT(tick()));
+}
+
+QRectF Latch::boundingRect() const {
+ return QRectF(-PENW / 2, -PENW / 2, WIDTH + PENW, height + PENW);
+}
+
+void Latch::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) {
+ painter->drawRect(0, 0, WIDTH, height);
+ // Now tick rectangle
+ const QPointF tickPolygon[] = {
+ QPointF(0, 0),
+ QPointF(WIDTH/2, WIDTH/2),
+ QPointF(WIDTH, 0)
+ };
+ painter->drawPolygon(tickPolygon, 3);
+}
+
+void Latch::setPos(qreal x, qreal y) {
+ QGraphicsObject::setPos(x, y);
+ for (unsigned i = 0; i < connectors.size(); i++) {
+ connectors[i].in->setPos(x, y + connectors_off[i]);
+ connectors[i].out->setPos(x + WIDTH, y + connectors_off[i]);
+ }
+}
+
+struct Latch::ConnectorPair Latch::new_connector(qreal cy) {
+ SANITY_ASSERT(cy < height, "Latch: Trying to create connector outside of latch height");
+ ConnectorPair cp;
+ cp.in = new Connector();
+ cp.out = new Connector();
+ connectors.append(cp);
+ connectors_off.append(cy);
+ setPos(x(), y()); // Update connectors position
+ return cp;
+}
+
+void Latch::tick() {
+ // TODO animate
+}
diff --git a/qtmips_gui/coreview/latch.h b/qtmips_gui/coreview/latch.h
new file mode 100644
index 0000000..a8738b3
--- /dev/null
+++ b/qtmips_gui/coreview/latch.h
@@ -0,0 +1,45 @@
+#ifndef LATCH_H
+#define LATCH_H
+
+#include <QGraphicsObject>
+#include <QList>
+#include "qtmipsexception.h"
+#include "qtmipsmachine.h"
+#include "../coreview.h"
+#include "connection.h"
+
+namespace coreview {
+
+class Latch : public QGraphicsObject {
+ Q_OBJECT
+public:
+ Latch(QtMipsMachine *machine, qreal height);
+
+ QRectF boundingRect() const;
+ void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget);
+
+ void setPos(qreal x, qreal y);
+
+ struct ConnectorPair { Connector *in, *out; };
+
+ struct ConnectorPair new_connector(qreal y); // Create new connectors pair that is given y from top of latch
+
+private slots:
+ void tick();
+
+private:
+ qreal height;
+ QList<ConnectorPair> connectors;
+ QList<qreal> connectors_off;
+
+};
+
+}
+
+#else
+
+namespace coreview {
+ class Latch;
+};
+
+#endif // LATCH_H
diff --git a/qtmips_gui/coreview/multiplexer.cpp b/qtmips_gui/coreview/multiplexer.cpp
new file mode 100644
index 0000000..57fa443
--- /dev/null
+++ b/qtmips_gui/coreview/multiplexer.cpp
@@ -0,0 +1,79 @@
+#include "multiplexer.h"
+
+using namespace coreview;
+
+//////////////////////
+#define WIDTH 20
+#define HEIGHT 20
+#define PENW 1
+//////////////////////
+
+Multiplexer::Multiplexer(unsigned size) {
+ this->size = size;
+ seton = 0;
+ ctlfrom = false;
+ con_ctl = new Connector();
+ con_out = new Connector();
+ con_in = new Connector*[size];
+ for (unsigned i = 0; i < size; i++)
+ con_in[i] = new Connector();
+ setPos(x(), y()); // Set connectors possitions
+}
+
+Multiplexer::~Multiplexer() {
+ delete con_ctl;
+ delete con_out;
+ for (unsigned i = 0; i < size; i++)
+ delete con_in[i];
+ delete con_in;
+}
+
+QRectF Multiplexer::boundingRect() const {
+ return QRectF(-PENW / 2, -PENW / 2, WIDTH + PENW, (HEIGHT * size) + PENW);
+}
+
+void Multiplexer::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) {
+ // Draw pointing line first so it isn't over the border lines
+ painter->setPen(QColor(200, 200, 200));
+ painter->drawLine(0, (HEIGHT / 2) + (seton * HEIGHT), WIDTH, (HEIGHT * size) / 2);
+
+ painter->setPen(QColor());
+ painter->drawLine(0, 0, 0, (HEIGHT * size)); // (|)
+ painter->drawLine(0, 0, WIDTH, WIDTH); // (\)
+ painter->drawLine(0, (HEIGHT * size), WIDTH, (HEIGHT * size) - WIDTH); // (/)
+ painter->drawLine(WIDTH, WIDTH, WIDTH, (HEIGHT * size) - WIDTH); // (|)
+}
+
+void Multiplexer::setPos(qreal x, qreal y) {
+ QGraphicsItem::setPos(x, y);
+ if (ctlfrom)
+ con_ctl->setPos(x + (WIDTH / 2), y + (WIDTH / 2));
+ else
+ con_ctl->setPos(x + (WIDTH / 2), y + (HEIGHT * size) - (WIDTH / 2));
+ con_out->setPos(x + WIDTH, y + ((HEIGHT *size) / 2));
+ for (unsigned i = 0; i < size; i++)
+ con_in[i]->setPos(x, y + (HEIGHT / 2) + (i * HEIGHT));
+}
+
+const Connector *Multiplexer::connector_ctl() const {
+ return con_ctl;
+}
+
+const Connector *Multiplexer::connector_out() const {
+ return con_out;
+}
+
+const Connector *Multiplexer::connector_in(unsigned i) const {
+ SANITY_ASSERT(i < size, "Multiplexer: requested out of range input connector");
+ return con_in[i];
+}
+
+void Multiplexer::set(unsigned i) {
+ seton = i;
+ update(boundingRect());
+}
+
+void Multiplexer::setCtl(bool up) {
+ ctlfrom = up;
+ setPos(x(), y()); // Update connectors
+}
diff --git a/qtmips_gui/coreview/multiplexer.h b/qtmips_gui/coreview/multiplexer.h
new file mode 100644
index 0000000..6aa11a6
--- /dev/null
+++ b/qtmips_gui/coreview/multiplexer.h
@@ -0,0 +1,41 @@
+#ifndef MULTIPLEXER_H
+#define MULTIPLEXER_H
+
+#include <QGraphicsItem>
+#include "qtmipsexception.h"
+#include "../coreview.h"
+#include "connection.h"
+
+namespace coreview {
+
+class Multiplexer : public QGraphicsItem {
+public:
+ Multiplexer(unsigned size);
+ ~Multiplexer();
+
+ QRectF boundingRect() const;
+ void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget);
+
+ void setPos(qreal x, qreal y);
+ const Connector *connector_ctl() const; // Control input
+ const Connector *connector_out() const; // Output
+ const Connector *connector_in(unsigned i) const; // Inputs
+
+ void set(unsigned i); // Set what value should be set as connected
+ void setCtl(bool up); // Set if control signal is from up or down (in default down)
+
+private:
+ bool ctlfrom;
+ unsigned size, seton;
+ Connector *con_ctl, *con_out, **con_in;
+};
+
+}
+
+#else
+
+namespace coreview {
+ class Multiplexer;
+}
+
+#endif // MULTIPLEXER_H
diff --git a/qtmips_gui/coreview/programcounter.cpp b/qtmips_gui/coreview/programcounter.cpp
new file mode 100644
index 0000000..32c907c
--- /dev/null
+++ b/qtmips_gui/coreview/programcounter.cpp
@@ -0,0 +1,48 @@
+#include "programcounter.h"
+
+using namespace coreview;
+
+//////////////////////
+#define WIDTH 80
+#define HEIGHT 100
+#define PENW 1
+//////////////////////
+
+ProgramCounter::ProgramCounter(QtMipsMachine *machine) : QGraphicsObject(nullptr), value(this), name(this) {
+ value.setText(QString("0x") + QString::number(machine->registers()->read_pc(), 16));
+ value.setPos(0, HEIGHT/2 - value.boundingRect().height()/2);
+ name.setText(QString("PC"));
+ name.setPos(WIDTH/2 - name.boundingRect().width()/2, 0);
+
+ connect(machine->registers(), SIGNAL(pc_update(std::uint32_t)), this, SLOT(pc_update(std::uint32_t)));
+
+ con_in = new Connector();
+ con_out = new Connector();
+ setPos(x(), y()); // To set initial connectors positions
+}
+
+QRectF ProgramCounter::boundingRect() const {
+ return QRectF(-PENW / 2, -PENW / 2, WIDTH + PENW, HEIGHT + PENW);
+}
+
+void ProgramCounter::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) {
+ painter->drawRect(0, 0, WIDTH, HEIGHT);
+}
+
+void ProgramCounter::setPos(qreal x, qreal y) {
+ QGraphicsObject::setPos(x, y);
+ con_in->setPos(x, y + HEIGHT/2);
+ con_out->setPos(x + WIDTH, y + HEIGHT/2);
+}
+
+const Connector *ProgramCounter::connector_in() const {
+ return con_in;
+}
+
+const Connector *ProgramCounter::connector_out() const {
+ return con_out;
+}
+
+void ProgramCounter::pc_update(std::uint32_t val) {
+ value.setText(QString("0x") + QString::number(val, 16));
+}
diff --git a/qtmips_gui/coreview/programcounter.h b/qtmips_gui/coreview/programcounter.h
new file mode 100644
index 0000000..98b161d
--- /dev/null
+++ b/qtmips_gui/coreview/programcounter.h
@@ -0,0 +1,42 @@
+#ifndef PROGRAMCOUNTER_H
+#define PROGRAMCOUNTER_H
+
+#include <QGraphicsObject>
+#include <QPainter>
+#include "qtmipsmachine.h"
+#include "../coreview.h"
+#include "connection.h"
+
+namespace coreview {
+
+class ProgramCounter : public QGraphicsObject {
+ Q_OBJECT
+public:
+ ProgramCounter(QtMipsMachine *machine);
+
+ QRectF boundingRect() const;
+ void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget);
+
+ void setPos(qreal x, qreal y);
+ const Connector *connector_in() const;
+ const Connector *connector_out() const;
+
+private slots:
+ void pc_update(std::uint32_t val);
+
+private:
+ QGraphicsSimpleTextItem value;
+ QGraphicsSimpleTextItem name;
+
+ Connector *con_in, *con_out;
+};
+
+}
+
+#else
+
+namespace coreview {
+ class ProgramCounter;
+};
+
+#endif // PROGRAMCOUNTER_H
diff --git a/qtmips_gui/mainwindow.cpp b/qtmips_gui/mainwindow.cpp
index ea48736..157baf4 100644
--- a/qtmips_gui/mainwindow.cpp
+++ b/qtmips_gui/mainwindow.cpp
@@ -18,11 +18,11 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) {
registers->hide();
// Connect signals from menu
- QObject::connect(ui->actionExit, SIGNAL(triggered(bool)), this, SLOT(close()));
- QObject::connect(ui->actionNew, SIGNAL(triggered(bool)), this, SLOT(new_machine()));
- QObject::connect(ui->actionCache, SIGNAL(triggered(bool)), this, SLOT(show_cache_content()));
- QObject::connect(ui->actionCache_statistics, SIGNAL(triggered(bool)), this, SLOT(show_cache_statictics()));
- QObject::connect(ui->actionRegisters, SIGNAL(triggered(bool)), this, SLOT(show_registers()));
+ connect(ui->actionExit, SIGNAL(triggered(bool)), this, SLOT(close()));
+ connect(ui->actionNew, SIGNAL(triggered(bool)), this, SLOT(new_machine()));
+ connect(ui->actionCache, SIGNAL(triggered(bool)), this, SLOT(show_cache_content()));
+ connect(ui->actionCache_statistics, SIGNAL(triggered(bool)), this, SLOT(show_cache_statictics()));
+ connect(ui->actionRegisters, SIGNAL(triggered(bool)), this, SLOT(show_registers()));
// Restore application state from settings
restoreState(settings->value("windowState").toByteArray());
@@ -39,6 +39,8 @@ MainWindow::~MainWindow() {
delete cache_statictics;
delete registers;
delete ui;
+ if (machine != nullptr)
+ delete machine;
}
void MainWindow::start() {
@@ -50,9 +52,19 @@ void MainWindow::create_core(MachineConfig *config) {
// Create machine
machine = new QtMipsMachine(config);
// Create machine view
- coreview = new CoreView(this);
+ coreview = new CoreView(this, machine);
this->setCentralWidget(coreview);
- // TODO connect signals
+
+ machine->set_speed(1000); // Set default speed to 1 sec
+
+ // Connect machine signals
+ connect(ui->actionRun, SIGNAL(triggered(bool)), machine, SLOT(play()));
+ connect(ui->actionPause, SIGNAL(triggered(bool)), machine, SLOT(pause()));
+ connect(ui->actionStep, SIGNAL(triggered(bool)), machine, SLOT(step()));
+ connect(ui->actionRestart, SIGNAL(triggered(bool)), machine, SLOT(restart()));
+
+ // Setup docks
+ registers->setup(machine);
}
void MainWindow::new_machine() {
diff --git a/qtmips_gui/qtmips_gui.pro b/qtmips_gui/qtmips_gui.pro
index ee2bd6e..da91449 100644
--- a/qtmips_gui/qtmips_gui.pro
+++ b/qtmips_gui/qtmips_gui.pro
@@ -19,7 +19,11 @@ SOURCES += \
coreview.cpp \
registersdock.cpp \
cachestatistics.cpp \
- cachecontent.cpp
+ cachecontent.cpp \
+ coreview/programcounter.cpp \
+ coreview/multiplexer.cpp \
+ coreview/connection.cpp \
+ coreview/latch.cpp
HEADERS += \
mainwindow.h \
@@ -27,7 +31,11 @@ HEADERS += \
coreview.h \
registersdock.h \
cachestatistics.h \
- cachecontent.h
+ cachecontent.h \
+ coreview/programcounter.h \
+ coreview/multiplexer.h \
+ coreview/connection.h \
+ coreview/latch.h
FORMS += \
NewDialog.ui \