Loading files
c730ec352This file is pinned to the selected release. Download the original to open it in your design tool.
import { DrawTraceToolBar } from "drawtracetoolbar.slint";
import { AddPadToolBar } from "addpadtoolbar.slint";
import { AddViaToolBar } from "addviatoolbar.slint";
import { DrawPolygonToolBar } from "drawpolygontoolbar.slint";
import { AddStrokeTextToolBar } from "addstroketexttoolbar.slint";
import { DrawPlaneToolBar } from "drawplanetoolbar.slint";
import { DrawZoneToolBar } from "drawzonetoolbar.slint";
import { AddHoleToolBar } from "addholetoolbar.slint";
import { Tab } from "../../tab.slint";
import {
Button,
LinkText,
MenuItem,
MenuPopup,
MessageBox,
SceneButton,
SliderSceneButton,
Spinner,
TextInput,
ToolBar,
ToolButton,
} from "../../widgets.slint";
import {
Backend,
Board2dTabData,
BoardAction,
BoardData,
Constants,
Data,
DismissableMessageAction,
EditorCommandSet as Cmd,
EditorTool,
OrganizationDbData,
PanelPage,
ProjectData,
TabAction,
WindowSectionData,
} from "../../api.slint";
export component Board2dTab inherits Tab {
in property <int> section-index;
in-out property <WindowSectionData> section;
property <[Board2dTabData]> tabs: section.board-2d-tabs;
property <int> index: section.current-tab-index;
property <ProjectData> project: Data.projects[tabs[index].project-index];
property <BoardData> board: project.boards[tabs[index].board-index];
property <length> toolbar-space: view-buttons-l.x - toolbuttons-l.x - toolbuttons-l.width - 10px;
property <length> toolbutton-offset-y: (toolbar-r.width > toolbar-space) ? toolbar-r.height : 0;
property <EditorTool> tool: tabs[index].tool;
function tool-triggered(action: TabAction) {
Data.set-current-tab(section-index, index);
Backend.trigger-tab(section-index, index, action);
}
enabled: true;
function update-tab-data() {
tabs[index].scene-image-pos = self.absolute-position;
}
init => {
update-tab-data();
}
// Needed for https://github.com/LibrePCB/LibrePCB/issues/1725.
changed section-index => {
update-tab-data();
}
// Needed for https://github.com/LibrePCB/LibrePCB/issues/1725.
changed index => {
update-tab-data();
}
changed absolute-position => {
update-tab-data();
}
// When the tool changes, move focus to scene to make tabulator & escape
// working correctly.
changed tool => {
root.focus();
}
if Data.preview-mode: Rectangle {
background: black;
}
img := Image {
width: 100%;
height: 100%;
source: Backend.render-scene(section-index, root.width, root.height, 0, tabs[index].frame);
accessible-role: none;
}
ta := TouchArea {
mouse-cursor: tabs[index].tool-cursor;
// Show overlay, if text is not empty.
if !tabs[index].tool-overlay-text.is-empty: Rectangle {
x: 2 * toolbuttons-l.x + toolbuttons-l.width;
y: toolbuttons-l.y;
width: min(self.preferred-width, 0.5 * parent.width);
height: self.preferred-height;
background: Data.theme.control;
border-radius: 3px;
border-width: 1px;
border-color: Data.theme.control-border;
clip: true;
VerticalLayout {
padding: 6px;
overlay-txt := TextInput {
horizontal-alignment: left;
vertical-alignment: top;
font-family: "Noto Sans Mono";
color: Data.theme.control-text;
text: tabs[index].tool-overlay-text;
read-only: true;
}
}
}
toolbar-r := ToolBar {
y: -1px;
width: min(self.preferred-width, root.width - 10px);
visible: self.width > 30px;
if tabs[index].tool == EditorTool.wire: trace-toolbar := DrawTraceToolBar {
section-index: section-index;
section: section;
show-via-size: root.width > 755px;
}
if tabs[index].tool == EditorTool.via: via-toolbar := AddViaToolBar {
section-index: section-index;
section: section;
}
if tabs[index].tool == EditorTool.pad-tht: pad-tht-toolbar := AddPadToolBar {
section-index: section-index;
section: section;
show-side-selector: false;
show-drill-edt: true;
show-ratio-sw: root.width > 850px;
show-pressfit-sw: root.width > 900px;
fiducial-mode: tabs[index].tool-fiducial;
}
if tabs[index].tool == EditorTool.pad-smt: pad-smt-toolbar := AddPadToolBar {
section-index: section-index;
section: section;
show-side-selector: true;
show-drill-edt: false;
show-pressfit-sw: false;
fiducial-mode: tabs[index].tool-fiducial;
}
if tabs[index].tool == EditorTool.polygon: polygon-toolbar := DrawPolygonToolBar {
section-index: section-index;
section: section;
}
if tabs[index].tool == EditorTool.text: text-toolbar := AddStrokeTextToolBar {
section-index: section-index;
section: section;
}
if tabs[index].tool == EditorTool.plane: plane-toolbar := DrawPlaneToolBar {
section-index: section-index;
section: section;
}
if tabs[index].tool == EditorTool.zone: zone-toolbar := DrawZoneToolBar {
section-index: section-index;
section: section;
}
if tabs[index].tool == EditorTool.hole: hole-toolbar := AddHoleToolBar {
section-index: section-index;
section: section;
}
}
if tabs[index].tool == EditorTool.select: VerticalLayout {
width: min(self.preferred-width, 0.7 * parent.width, 550px);
padding: 20px;
spacing: 10px;
if tabs[index].empty-schematics-msg.visible: empty-schematics-msg := MessageBox {
title: @tr("Empty Schematics");
supports-dont-show-again: tabs[index].empty-schematics-msg.supports-dont-show-again;
Text {
wrap: word-wrap;
color: parent.text-color;
text: @tr("Only components existing in the schematics can be added to a board, but the schematics of this project are currently empty. Please draw the schematics first before starting with the board design.");
}
HorizontalLayout {
alignment: start;
empty-schematics-help-btn := LinkText {
text-color: empty-schematics-msg.text-color;
text-color-hovered: empty-schematics-msg.hover-color;
wrap: word-wrap;
text: "▸ " + @tr("Learn how to create schematics");
status-tip: Constants.user-manual-create-schematics-url;
clicked => {
Backend.open-url(self.status-tip);
}
}
}
dismiss-clicked => {
tabs[index].empty-schematics-msg.action = DismissableMessageAction.dismiss;
}
dont-show-again-clicked => {
tabs[index].empty-schematics-msg.action = DismissableMessageAction.dont-show-again;
}
}
if tabs[index].setup-design-rules-msg.visible: setup-design-rules-msg := MessageBox {
title: @tr("Set Up Design Rules");
supports-dont-show-again: tabs[index].setup-design-rules-msg.supports-dont-show-again;
Text {
wrap: word-wrap;
color: parent.text-color;
text: @tr("Before starting with the board design, it is highly recommended to set up the design rules to be used for this board. Just choose one of the presets below, or manually specify the rules.");
}
Rectangle {
clip: true; // The PCB design rules listed may be too long to show.
VerticalLayout {
padding-left: 10px;
spacing: 3px;
HorizontalLayout {
alignment: start;
Text {
color: setup-design-rules-msg.text-color;
font-weight: 700;
text: "• ";
}
setup-design-rules-msg-default-btn := LinkText {
text-color: setup-design-rules-msg.text-color;
text-color-hovered: setup-design-rules-msg.hover-color;
font-weight: 700;
text: @tr("Default rules, suitable for most PCB manufacturers");
clicked => {
tabs[index].set-design-rules = "default";
}
}
}
property <[OrganizationDbData]> organizations: Backend.get-organizations-with-design-rules();
for i in min(organizations.length, 8): HorizontalLayout {
property <OrganizationDbData> organization: organizations[i];
alignment: start;
Text {
color: setup-design-rules-msg.text-color;
font-weight: (organization.priority == 100) ? 700 : 400;
text: "• \{organization.name}: ";
}
for rules[k] in organization.pcb-design-rules: HorizontalLayout {
if k > 0: Text {
color: setup-design-rules-msg.text-color;
font-weight: (organization.priority == 100) ? 700 : 400;
text: " | ";
}
setup-design-rules-msg-item-btn := LinkText {
text-color: setup-design-rules-msg.text-color;
text-color-hovered: setup-design-rules-msg.hover-color;
font-weight: (organization.priority == 100) ? 700 : 400;
font-italic: true;
text: rules.name;
clicked => {
tabs[index].set-design-rules = organization.uuid + ":" + rules.uuid;
}
}
}
}
HorizontalLayout {
alignment: start;
Text {
color: setup-design-rules-msg.text-color;
font-weight: 700;
text: "• ";
}
setup-design-rules-msg-manual-btn := LinkText {
text-color: setup-design-rules-msg.text-color;
text-color-hovered: setup-design-rules-msg.hover-color;
text: "Other Options / Manual Setup";
clicked => {
tabs[index].setup-design-rules-msg.action = DismissableMessageAction.dismiss;
Backend.trigger-board(tabs[index].project-index, tabs[index].board-index, BoardAction.open-setup-dialog);
}
}
}
}
}
dismiss-clicked => {
tabs[index].setup-design-rules-msg.action = DismissableMessageAction.dismiss;
}
dont-show-again-clicked => {
tabs[index].setup-design-rules-msg.action = DismissableMessageAction.dont-show-again;
}
}
if tabs[index].place-devices-msg.visible: place-devices-msg := MessageBox {
title: @tr("Get Started");
supports-dont-show-again: tabs[index].place-devices-msg.supports-dont-show-again;
Text {
wrap: word-wrap;
color: parent.text-color;
text: @tr("Start designing the board by placing all devices which are part of the schematics.");
}
if Data.panel-page == PanelPage.place-devices: VerticalLayout {
padding-left: 10px;
spacing: 3px;
Text {
wrap: word-wrap;
color: place-devices-msg.text-color;
text: "1. " + @tr("Select an unplaced component in the list.");
}
Text {
wrap: word-wrap;
color: place-devices-msg.text-color;
text: "2. " + @tr("Choose the desired device (which defines the package to use).");
}
Text {
wrap: word-wrap;
color: place-devices-msg.text-color;
text: "3. " + @tr("Choose the desired footprint variant if there are multiple.");
}
Text {
wrap: word-wrap;
color: place-devices-msg.text-color;
text: "4. " + @tr("Confirm with the button and place the device interactively with the cursor.");
}
}
if Data.panel-page != PanelPage.place-devices: place-devices-btn := Button {
border-radius: self.height / 2;
primary: true;
text: @tr("Place Devices");
clicked => {
Data.panel-page = PanelPage.place-devices;
}
}
HorizontalLayout {
alignment: start;
place-devices-help-btn := LinkText {
text-color: place-devices-msg.text-color;
text-color-hovered: place-devices-msg.hover-color;
wrap: word-wrap;
text: "▸ " + @tr("Learn how to create boards");
status-tip: Constants.user-manual-create-board-url;
clicked => {
Backend.open-url(self.status-tip);
}
}
}
dismiss-clicked => {
tabs[index].place-devices-msg.action = DismissableMessageAction.dismiss;
}
dont-show-again-clicked => {
tabs[index].place-devices-msg.action = DismissableMessageAction.dont-show-again;
}
}
}
toolbuttons-l := VerticalLayout {
x: 7px;
y: 7px + toolbutton-offset-y;
width: self.preferred-width;
height: parent.height - 2 * self.y;
alignment: start;
spacing: 5px;
tool-select-btn := ToolButton {
cmd: Cmd.tool-select;
checked: tabs[index].tool == EditorTool.select;
clicked => {
tool-triggered(TabAction.tool-select);
}
}
tool-trace-btn := ToolButton {
cmd: Cmd.tool-trace;
icon: @image-url("../../../../../img/actions/draw_wire.png");
checked: tabs[index].tool == EditorTool.wire;
clicked => {
tool-triggered(TabAction.tool-wire);
}
}
tool-via-btn := ToolButton {
cmd: Cmd.tool-via;
has-menu: true;
checked: (tabs[index].tool == EditorTool.via) || (tabs[index].tool == EditorTool.pad-tht) || (tabs[index].tool == EditorTool.pad-smt);
clicked => {
tool-triggered(TabAction.tool-via);
}
right-clicked => {
pads-popup.show();
}
pads-popup := MenuPopup {
x: parent.width + 5px;
y: 0;
tool-pad-tht-item := MenuItem {
cmd: Cmd.tool-pad-tht;
icon: @image-url("../../../../../img/actions/add_tht_pad.png");
icon-color: transparent; // Do not colorize.
clicked => {
Backend.trigger-tab(section-index, index, TabAction.tool-pad-tht);
}
}
tool-pad-smt-item := MenuItem {
cmd: Cmd.tool-pad-smt;
icon: @image-url("../../../../../img/actions/add_smt_pad.png");
icon-color: transparent; // Do not colorize.
clicked => {
Backend.trigger-tab(section-index, index, TabAction.tool-pad-smt);
}
}
tool-pad-thermal-item := MenuItem {
cmd: Cmd.tool-pad-thermal;
icon: @image-url("../../../../../img/actions/add_smt_pad.png");
icon-color: transparent; // Do not colorize.
clicked => {
Backend.trigger-tab(section-index, index, TabAction.tool-pad-thermal);
}
}
// BGA pads probably really make no sense in a board(?).
// tool-pad-bga-item := MenuItem {
// cmd: Cmd.tool-pad-bga;
// icon: @image-url("../../../../../img/actions/add_smt_pad.png");
// icon-color: transparent; // Do not colorize.
//
// clicked => {
// Backend.trigger-tab(section-index, index, TabAction.tool-pad-bga);
// }
// }
tool-pad-edge-connector-item := MenuItem {
cmd: Cmd.tool-pad-edge-connector;
icon: @image-url("../../../../../img/actions/add_smt_pad.png");
icon-color: transparent; // Do not colorize.
clicked => {
Backend.trigger-tab(section-index, index, TabAction.tool-pad-edge-connector);
}
}
tool-pad-test-point-item := MenuItem {
cmd: Cmd.tool-pad-test-point;
icon: @image-url("../../../../../img/actions/add_smt_pad.png");
icon-color: transparent; // Do not colorize.
clicked => {
Backend.trigger-tab(section-index, index, TabAction.tool-pad-test-point);
}
}
tool-pad-local-fiducial-item := MenuItem {
cmd: Cmd.tool-pad-local-fiducial;
icon: @image-url("../../../../../img/actions/add_smt_pad.png");
icon-color: transparent; // Do not colorize.
clicked => {
Backend.trigger-tab(section-index, index, TabAction.tool-pad-local-fiducial);
}
}
tool-pad-global-fiducial-item := MenuItem {
cmd: Cmd.tool-pad-global-fiducial;
icon: @image-url("../../../../../img/actions/add_smt_pad.png");
icon-color: transparent; // Do not colorize.
clicked => {
Backend.trigger-tab(section-index, index, TabAction.tool-pad-global-fiducial);
}
}
}
}
tool-polygon-btn := ToolButton {
cmd: Cmd.tool-polygon;
checked: tabs[index].tool == EditorTool.polygon;
clicked => {
tool-triggered(TabAction.tool-polygon);
}
}
tool-text-btn := ToolButton {
cmd: Cmd.tool-text;
checked: tabs[index].tool == EditorTool.text;
clicked => {
tool-triggered(TabAction.tool-text);
}
}
tool-plane-btn := ToolButton {
cmd: Cmd.tool-plane;
checked: tabs[index].tool == EditorTool.plane;
clicked => {
tool-triggered(TabAction.tool-plane);
}
}
tool-hole-btn := ToolButton {
cmd: Cmd.tool-hole;
checked: tabs[index].tool == EditorTool.hole;
clicked => {
tool-triggered(TabAction.tool-hole);
}
}
tool-zone-btn := ToolButton {
cmd: Cmd.tool-zone;
checked: tabs[index].tool == EditorTool.zone;
clicked => {
tool-triggered(TabAction.tool-zone);
}
}
tool-measure-btn := ToolButton {
cmd: Cmd.tool-measure;
checked: tabs[index].tool == EditorTool.measure;
clicked => {
tool-triggered(TabAction.tool-measure);
}
}
}
view-buttons-l := VerticalLayout {
x: parent.width - self.preferred-width - 7px;
y: toolbutton-offset-y + 7px;
width: self.preferred-width;
zoom-fit-btn := SceneButton {
x: parent.width - self.width;
icon: @image-url("../../../../font-awesome/svgs/solid/expand.svg");
icon-scale: 0.9;
base-color: tabs[index].background-color;
base-text-color: tabs[index].foreground-color;
clicked => {
Backend.trigger-tab(section-index, index, TabAction.zoom-fit);
}
}
zoom-in-btn := SceneButton {
x: parent.width - self.width;
icon: @image-url("../../../../bootstrap-icons/icons/zoom-in.svg");
base-color: tabs[index].background-color;
base-text-color: tabs[index].foreground-color;
clicked => {
Backend.trigger-tab(section-index, index, TabAction.zoom-in);
}
}
zoom-out-btn := SceneButton {
x: parent.width - self.width;
icon: @image-url("../../../../bootstrap-icons/icons/zoom-out.svg");
base-color: tabs[index].background-color;
base-text-color: tabs[index].foreground-color;
clicked => {
Backend.trigger-tab(section-index, index, TabAction.zoom-out);
}
}
flip-btn := SceneButton {
x: parent.width - self.width;
style: self.checked ? checkbox : button;
icon-scale: self.checked ? 1.0 : 1.1;
icon: Cmd.flip-view.icon;
tooltip: Cmd.flip-view.text;
base-color: tabs[index].background-color;
base-text-color: tabs[index].foreground-color;
checked: tabs[index].flip-view;
clicked => {
tabs[index].flip-view = !tabs[index].flip-view;
}
}
background-image-btn := SliderSceneButton {
x: parent.width - self.width;
style: self.slider-enabled ? checkbox : button;
icon-scale: self.slider-enabled ? 0.8 : 0.9;
icon: Cmd.toggle-background-image.icon;
tooltip: self.slider-enabled ? @tr("Background Image") : Cmd.toggle-background-image.text;
base-color: tabs[index].background-color;
base-text-color: tabs[index].foreground-color;
value: tabs[index].background-image-alpha;
slider-enabled: tabs[index].background-image-set;
button-clicked => {
Backend.trigger-tab(section-index, index, TabAction.toggle-background-image);
}
value-changed(value) => {
tabs[index].background-image-alpha = value;
}
}
toggle-3d-btn := SceneButton {
x: parent.width - self.width;
text: "3D";
base-color: tabs[index].background-color;
base-text-color: tabs[index].foreground-color;
clicked => {
Backend.trigger-board(tabs[index].project-index, tabs[index].board-index, BoardAction.open-3d);
}
}
Rectangle {
height: 5px;
}
if tabs[index].refreshing: spinner := Spinner {
x: toggle-3d-btn.x + (toggle-3d-btn.width - self.width) / 2;
width: 20px;
height: self.width;
foreground-color: tabs[index].foreground-color;
indeterminate: true;
}
}
VerticalLayout {
x: 5px;
y: parent.height - self.height - self.x;
width: self.preferred-width;
height: self.preferred-height;
if board.drc.unapproved > 0: unapproved-messages-btn := LinkText {
text-color: tabs[index].foreground-color.with-alpha(0.7);
text-color-hovered: tabs[index].foreground-color;
text: @tr("DRC Messages: {}", board.drc.unapproved);
accessible-role: none;
clicked => {
Data.panel-page = PanelPage.rule-check;
}
}
if tabs[index].unplaced-components.length > 0: unplaced-devices-btn := LinkText {
text-color: tabs[index].foreground-color.with-alpha(0.7);
text-color-hovered: tabs[index].foreground-color;
text: @tr("Unplaced: {}", tabs[index].unplaced-components.length);
accessible-role: none;
clicked => {
Data.panel-page = PanelPage.place-devices;
}
}
}
pointer-event(event) => {
if event.kind == PointerEventKind.down {
Data.set-current-tab(section-index, index);
if !root.has-focus {
root.focus();
}
}
Backend.scene-pointer-event(section-index, self.mouse-x, self.mouse-y, event, 0);
}
scroll-event(event) => {
Backend.scene-scrolled(section-index, self.mouse-x, self.mouse-y, event, 0) ? accept : reject
}
}
focus-gained(reason) => {
Data.set-current-tab(section-index, index);
}
key-pressed(event) => {
// If the focus is in the command toolbar, make the Escape key moving
// the focus back to the graphics scene without triggering any action.
if (event.text == Key.Escape) && (!root.has-focus) {
root.focus();
return accept;
}
reject
}
key-released(event) => {
Backend.scene-key-released(section-index, event) ? accept : reject
}
}