====== QMLで7セグメント風表示 ======
import QtQuick
Canvas {
id: canvas
width: 100; height: 160
// 入力プロパティ
property int value: 0
property bool showDot: false
property real reading: 0 // 現在のセンサー数値
property real threshold1: 0 // アンバーになる値
property real threshold2: 0 // 赤になる値
// デザイン設定
property real skewAngle: 8
property real gap: 4
property real thickness: 16
// 状態に基づいた色の自動判定
readonly property color activeColor: {
if (reading >= threshold2) return "#CC0000"; // やや暗めの赤
if (reading >= threshold1) return "#FFBF00"; // アンバー
return "#006622"; // 通常時の暗めの緑
}
readonly property color inactiveColor: "#051505" // 消灯時(背景に馴染ませる)
// 再描画トリガー
onActiveColorChanged: requestPaint()
onValueChanged: requestPaint()
onShowDotChanged: requestPaint()
antialiasing: true
onPaint: {
var ctx = getContext("2d");
ctx.reset();
ctx.clearRect(0, 0, width, height);
var angleRad = skewAngle * Math.PI / 180;
ctx.transform(1, 0, -Math.tan(angleRad), 1, height * Math.tan(angleRad), 0);
var p = [
0x3F, 0x06, 0x5B, 0x4F, 0x66, 0x6D, 0x7D, 0x27, 0x7F, 0x6F
][value % 10] || 0;
function drawShape(points, on) {
ctx.beginPath();
ctx.moveTo(points[0].x, points[0].y);
for (var i = 1; i < points.length; i++) ctx.lineTo(points[i].x, points[i].y);
ctx.closePath();
ctx.fillStyle = on ? activeColor : inactiveColor;
// 閾値を超えた時だけ少し光らせる演出
ctx.shadowBlur = (on && reading >= threshold1) ? 10 : 0;
ctx.shadowColor = activeColor;
ctx.fill();
}
var w = width * 0.75;
var h = height;
var t = thickness;
var g = gap;
// セグメント座標計算 (六角形)
drawShape([{x:t+g, y:0}, {x:w-t-g, y:0}, {x:w-t*1.5-g, y:t}, {x:t*1.5+g, y:t}], p & 0x01); // A
drawShape([{x:w-t, y:t+g}, {x:w, y:t*1.5+g}, {x:w, y:h/2-t/2-g}, {x:w-t, y:h/2-g}], p & 0x02); // B
drawShape([{x:w-t, y:h/2+g}, {x:w, y:h/2+t/2+g}, {x:w, y:h-t*1.5-g}, {x:w-t, y:h-t-g}], p & 0x04); // C
drawShape([{x:t*1.5+g, y:h-t}, {x:w-t*1.5-g, y:h-t}, {x:w-t-g, y:h}, {x:t+g, y:h}], p & 0x08); // D
drawShape([{x:0, y:h/2+t/2+g}, {x:t, y:h/2+g}, {x:t, y:h-t-g}, {x:0, y:h-t*1.5-g}], p & 0x10); // E
drawShape([{x:0, y:t*1.5+g}, {x:t, y:t+g}, {x:t, y:h/2-g}, {x:0, y:h/2-t/2-g}], p & 0x20); // F
drawShape([{x:t+g, y:h/2}, {x:t*1.5+g, y:h/2-t/2}, {x:w-t*1.5-g, y:h/2-t/2}, {x:w-t-g, y:h/2}, {x:w-t*1.5-g, y:h/2+t/2}, {x:t*1.5+g, y:h/2+t/2}], p & 0x40); // G
// ドット
ctx.beginPath();
ctx.arc(w + t/2, h - t/2, t/2.5, 0, Math.PI * 2);
ctx.fillStyle = showDot ? activeColor : inactiveColor;
ctx.fill();
}
}
import QtQuick
Row {
id: root
spacing: 10
// 入力プロパティ
property real reading: 0
property int digits: 4 // 表示する桁数
property int decimalPlaces: 0 // 小数点以下の桁数(0なら整数)
property real threshold1: 0
property real threshold2: 0
// デザイン設定(SevenSegに引き継ぐ)
property color activeColor: "green" // 基本色(SevenSeg側で上書きも可)
property real skewAngle: 8
property real thickness: 16
Repeater {
model: root.digits
SevenSeg {
// 左から数えて何番目の桁かを計算
// index 0 が一番左(大きい桁)
readonly property int power: root.digits - index - 1 - root.decimalPlaces
value: Math.floor(Math.abs(root.reading) / Math.pow(10, power)) % 10
// 小数点の位置判定
showDot: (root.decimalPlaces > 0 && power === 0)
// 共通プロパティの流し込み
reading: root.reading
threshold1: root.threshold1
threshold2: root.threshold2
skewAngle: root.skewAngle
thickness: root.thickness
// 桁が足りない場合にゼロを暗くするか消すロジック(オプション)
opacity: (power > 0 && Math.pow(10, power) > Math.abs(root.reading) && index < root.digits - root.decimalPlaces - 1) ? 0.2 : 1.0
}
}
}
import QtQuick
Window {
visible: true
width: 600; height: 400
color: "black"
Column {
anchors.centerIn: parent
spacing: 40
// CO2濃度 (4桁整数)
SevenSegMulti {
reading: 1250
digits: 4
threshold1: 1000
threshold2: 2000
}
// 温度 (3桁、小数点1位固定)
SevenSegMulti {
reading: 28.5
digits: 3
decimalPlaces: 1
threshold1: 30
threshold2: 35
}
// 湿度 (2桁整数)
SevenSegMulti {
reading: 55
digits: 2
threshold1: 70
threshold2: 80
}
}
}
TitleBar {
id: root
property string text: "タイトル"
property string font: "Noto Serif JP"
property string fontSize: 24
property string backgroundColor: "#000000"
property string foregroundColor: "#FF7F00"
Item {
Rectangle {
anchors.fill: parent
gradient: Gradient {
orientation: Gradient.Horizontal
GradientStop { position: 0.0; color: "#FF0000" }
GradientStop { position: 0.5; color: "#00FF00" }
GradientStop { position: 1.0; color: "#0000FF" }
}
padding: 0
Canvas {
id: canvas
anchors.fill: parent
property real labelHeight: label.contentHeight
property real labelWidth: label.contentWidth
onPaint: {
var ctx = getContext("2d")
ctx.reset()
ctx.clearRect(0, 0, width, height)
ctx.beginPath()
ctx.moveTo(0, 0)
ctx.lineTo(labelWidth + 20, 0)
ctx.lineTo(labelWidth + 20 + height / 4, height / 2)
ctx.lineTo(width - 1, height / 2)
ctx.lineTo(width - 1, height - 1)
ctx.lineTo(0, height - 1)
ctx.closePath()
ctx.fillStyle = "#000000"
ctx.fill()
}
Text {
id: label
text: root.text
color: root.foregroundColor
font.family: root.font
font.pixelSize: root.fontSize
horizontalAlignment: Text.AlignLeft
verticalAlignment: Text.AlignTop
padding: 10
}
}
}
}
}