Text-Erstellung mit Floating-Input-Box (Variante B)

Neuer Workflow: Klick "+ Text" in Topbar → Punkt im Viewport picken →
Floating Eto-Dialog erscheint neben dem Mauszeiger → User tippt → Enter
fuegt TextEntity mit Topbar-Settings ein. Esc bricht ab.

Backend (rhino/text_create.py):
- load_settings/save_settings — persistiert font/size/bold/italic in
  doc.Strings["dossier_text_settings"] (JSON)
- available_fonts() — System-Font-Namen via
  Rhino.DocObjects.Font.AvailableFontFaceNames
- _floating_input() — Eto.Dialog mit TextBox, ShowModal mit Rhino-
  MainWindow als Parent, positioniert bei Mouse.Position
- create_text() — RhinoGet.GetPoint → _floating_input → TextEntity
  mit Font/Size/Bold/Italic erstellen + AddText
- _apply_font() mit 2 Fallback-Pfaden (FontTable.FindOrCreate +
  Font.FromQuartetProperties) fuer RhinoCommon-Kompatibilitaet

Backend (oberleiste.py):
- CREATE_TEXT handler → text_create.create_text()
- SET_TEXT_SETTINGS handler → text_create.save_settings (merge partial)
- State payload: textSettings (immer) + textFonts (einmalig initial,
  via _fonts_sent Flag — Liste aendert sich nicht zur Laufzeit)

Frontend (OberleisteApp + rhinoBridge):
- createText() + setTextSettings() Bridge-Funktionen
- Text-Block 2x2 Grid analog Massstab:
  R1: Font-Dropdown (BarCombo mit text_fields icon) | Size-Input mit "m" suffix
  R2: B/I-Toggles (segmented pill mit accent-Fill bei active) | "+ Text" Button
- Hover-Logik analog View-Toggle (bg → bg-item-hover, color → accent-light)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-05-20 23:52:00 +02:00
parent 817455bbba
commit 2252ffd2f9
4 changed files with 361 additions and 1 deletions
+143 -1
View File
@@ -11,7 +11,7 @@ import {
deleteLayerCombination, openLayerCombinationsDialog,
openDossierSettings, openKameraPanel,
setMasseActive, openMasseSettings,
openAbout,
openAbout, createText, setTextSettings,
} from './lib/rhinoBridge'
const PRESETS = [
@@ -305,6 +305,8 @@ export default function OberleisteApp() {
overridesActivePreset: null, overridesPresets: [],
layerCombinations: [], layerCombinationActive: null,
massePresets: [], masseActiveId: null,
textSettings: { font: 'Helvetica', size: 0.20, bold: false, italic: false },
textFonts: [],
})
const [appliedScale, setAppliedScale] = useState(null)
const appliedScaleRef = useRef(null)
@@ -759,6 +761,146 @@ export default function OberleisteApp() {
)
})()}
<div style={sep} />
{/* ====== TEXT-Block 2-Reihen ======
Reihe 1: Font-Dropdown + Groesse (mm)
Reihe 2: B/I-Toggles + "Text einfuegen"-Button
*/}
{(() => {
const ts = state.textSettings || {}
const fonts = state.textFonts || []
const updateTs = (patch) => setTextSettings({ ...ts, ...patch })
const TEXT_W = 150
return (
<div style={{
display: 'grid', gridTemplateColumns: 'auto auto', gap: '4px 6px',
alignItems: 'center', flexShrink: 0,
}}>
{/* Reihe 1, Spalte 1: Font-Dropdown */}
<BarCombo
icon="text_fields"
value={ts.font || ''}
onChange={(v) => updateTs({ font: v })}
width={TEXT_W}
title="Schriftart"
>
{fonts.length === 0 && <option value=""></option>}
{fonts.map(f => <option key={f} value={f}>{f}</option>)}
</BarCombo>
{/* Reihe 1, Spalte 2: Groesse (mm) */}
<div style={{
display: 'inline-flex', alignItems: 'center', gap: 4,
height: BAR_H, padding: '0 10px',
background: 'var(--bg-input)',
border: '1px solid var(--border)',
borderRadius: 999,
flexShrink: 0, width: 90,
}}>
<input
type="number" step="0.01" min="0.01"
value={ts.size != null ? ts.size : 0.2}
onChange={(e) => {
const v = parseFloat(e.target.value)
if (!isNaN(v) && v > 0) updateTs({ size: v })
}}
style={{
flex: 1, minWidth: 0,
background: 'transparent', border: 'none', outline: 'none',
color: 'var(--text-primary)',
fontSize: 11, fontFamily: 'DM Mono, monospace',
padding: 0, textAlign: 'right',
appearance: 'auto',
}}
title="Texthoehe in Model-Units"
/>
<span style={{ fontSize: 9, color: 'var(--text-muted)' }}>m</span>
</div>
{/* Reihe 2, Spalte 1: B/I-Toggles */}
<div style={{
display: 'inline-flex',
border: '1px solid var(--border)', borderRadius: 999,
overflow: 'hidden', flexShrink: 0, width: TEXT_W,
}}>
<button
onClick={() => updateTs({ bold: !ts.bold })}
onMouseEnter={(e) => {
if (ts.bold) return
e.currentTarget.style.background = 'var(--bg-item-hover)'
e.currentTarget.style.color = 'var(--accent-light)'
}}
onMouseLeave={(e) => {
if (ts.bold) return
e.currentTarget.style.background = 'var(--bg-input)'
e.currentTarget.style.color = 'var(--text-primary)'
}}
style={{
flex: 1, height: BAR_H,
background: ts.bold ? 'var(--accent)' : 'var(--bg-input)',
color: ts.bold ? 'var(--bg-panel)' : 'var(--text-primary)',
border: 'none', cursor: 'pointer',
fontWeight: 700, fontSize: 11,
transition: 'background 0.15s, color 0.15s',
}}
title="Fett"
>B</button>
<button
onClick={() => updateTs({ italic: !ts.italic })}
onMouseEnter={(e) => {
if (ts.italic) return
e.currentTarget.style.background = 'var(--bg-item-hover)'
e.currentTarget.style.color = 'var(--accent-light)'
}}
onMouseLeave={(e) => {
if (ts.italic) return
e.currentTarget.style.background = 'var(--bg-input)'
e.currentTarget.style.color = 'var(--text-primary)'
}}
style={{
flex: 1, height: BAR_H,
background: ts.italic ? 'var(--accent)' : 'var(--bg-input)',
color: ts.italic ? 'var(--bg-panel)' : 'var(--text-primary)',
border: 'none', borderLeft: '1px solid var(--border)',
cursor: 'pointer',
fontStyle: 'italic', fontSize: 11,
transition: 'background 0.15s, color 0.15s',
}}
title="Kursiv"
>I</button>
</div>
{/* Reihe 2, Spalte 2: "Text einfuegen" Button */}
<button
onClick={() => createText()}
onMouseEnter={(e) => {
e.currentTarget.style.background = 'var(--bg-item-hover)'
e.currentTarget.style.borderColor = 'var(--accent)'
e.currentTarget.style.color = 'var(--accent-light)'
}}
onMouseLeave={(e) => {
e.currentTarget.style.background = 'var(--bg-input)'
e.currentTarget.style.borderColor = 'var(--border)'
e.currentTarget.style.color = 'var(--text-primary)'
}}
style={{
width: 90, height: BAR_H,
background: 'var(--bg-input)',
color: 'var(--text-primary)',
border: '1px solid var(--border)',
borderRadius: 999,
display: 'inline-flex', alignItems: 'center', justifyContent: 'center',
gap: 4, cursor: 'pointer',
fontSize: 11, fontWeight: 500,
transition: 'background 0.15s, color 0.15s, border-color 0.15s',
}}
title="Position picken → Text tippen → Enter"
>
<Icon name="add" size={12} />
Text
</button>
</div>
)
})()}
{/* Snap-Toggles (Ortho/Grid/OSnap) sind in Rhinos eigener Footer-Bar
schon vorhanden — hier rausgenommen um Doppelung zu vermeiden. */}
+2
View File
@@ -171,6 +171,8 @@ export function saveOverridesPreset(name) { send('SAVE_OVERRIDES_PRESET', { name
export function openOverridesPanel() { send('OPEN_OVERRIDES_PANEL', {}) }
export function openKameraPanel() { send('OPEN_KAMERA_PANEL', {}) }
export function openAbout() { send('OPEN_ABOUT', {}) }
export function createText() { send('CREATE_TEXT', {}) }
export function setTextSettings(settings) { send('SET_TEXT_SETTINGS', { settings }) }
// --- Masse (in Oberleiste + Satellite-Fenster MasseSettings) ---
// Topbar: aktives Mass setzen + Settings-Fenster oeffnen