forked from 1b-agent/opencadstudio-upstream
Merge pull request #1795 from MarcelP102/Select-Color-Redesign
[UX] Select Color redesign and feature parity Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
commit
843cbcd607
56 files changed
+4716
-606
No files matched your search
Generated
+12
@@ -2593,6 +2593,8 @@ dependencies = [
|
||||
"iced_core",
|
||||
"iced_fonts_macros",
|
||||
"iced_widget",
|
||||
"num-format",
|
||||
"num-traits",
|
||||
"web-time",
|
||||
]
|
||||
|
||||
@@ -3868,6 +3870,16 @@ dependencies = [
|
||||
"syn 2.0.119",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-format"
|
||||
version = "0.4.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a652d9771a63711fd3c3deb670acfbe5c30a4072e664d7a3bf5a9e1056ac72c3"
|
||||
dependencies = [
|
||||
"arrayvec",
|
||||
"itoa",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-integer"
|
||||
version = "0.1.46"
|
||||
|
||||
+1
-1
@@ -30,7 +30,7 @@ url = "2"
|
||||
iced = { git = "https://github.com/iced-rs/iced.git", rev = "23604ff22ab0aad9e00b9327cb7b8546ed84db39", features = ["image", "svg", "advanced", "canvas", "smol", "markdown"] }
|
||||
iced_runtime = { git = "https://github.com/iced-rs/iced.git", rev = "23604ff22ab0aad9e00b9327cb7b8546ed84db39" }
|
||||
iced_core = { git = "https://github.com/iced-rs/iced.git", rev = "23604ff22ab0aad9e00b9327cb7b8546ed84db39" }
|
||||
iced_aw = { git = "https://github.com/HakanSeven12/iced_aw.git", branch = "agent/fix-iced-fonts", features = ["menu", "context_menu", "color_picker", "drop_down"] }
|
||||
iced_aw = { git = "https://github.com/HakanSeven12/iced_aw.git", branch = "agent/fix-iced-fonts", features = ["menu", "context_menu", "drop_down", "number_input"] }
|
||||
bytemuck = { version = "1.25", features = ["derive"] }
|
||||
glam = { version = "0.33", features = ["bytemuck"] }
|
||||
rfd = "0.17"
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
# Built-in colour books
|
||||
|
||||
AutoCAD `.acb` colour books built into the Select Color panel. Each file
|
||||
carries its source and licence in its header comment.
|
||||
|
||||
| File | Book | Source and licence |
|
||||
|---|---|---|
|
||||
| `ral-classic.acb` | RAL CLASSIC, 216 approximate sRGB values | Adapted from Wikipedia, "List of RAL colours"; CC BY-SA 4.0 |
|
||||
|
||||
Values are approximate screen representations, not colour-binding
|
||||
references. RAL is a registered trademark of RAL gGmbH; this file is not
|
||||
affiliated with or endorsed by RAL gGmbH.
|
||||
@@ -0,0 +1,301 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
RAL Classic: non-official approximate sRGB screen values, 216 colours.
|
||||
Not colour-binding: use an official physical RAL reference for colour-critical work.
|
||||
Colour values adapted from Wikipedia contributors, "List of RAL colours"
|
||||
(https://en.wikipedia.org/wiki/List_of_RAL_colours), licensed CC BY-SA 4.0
|
||||
(https://creativecommons.org/licenses/by-sa/4.0/). This file is shared under the same licence.
|
||||
RAL is a registered trademark of RAL gGmbH; this file is not affiliated with,
|
||||
endorsed by or licensed by RAL gGmbH.
|
||||
-->
|
||||
<colorBook>
|
||||
<bookName>RAL CLASSIC</bookName>
|
||||
<majorVersion>1</majorVersion>
|
||||
<minorVersion>0</minorVersion>
|
||||
<colorPage>
|
||||
<colorEntry><colorName>RAL 1000</colorName><RGB8><red>205</red><green>186</green><blue>136</blue></RGB8></colorEntry>
|
||||
<colorEntry><colorName>RAL 1001</colorName><RGB8><red>208</red><green>176</green><blue>132</blue></RGB8></colorEntry>
|
||||
<colorEntry><colorName>RAL 1002</colorName><RGB8><red>210</red><green>170</green><blue>109</blue></RGB8></colorEntry>
|
||||
<colorEntry><colorName>RAL 1003</colorName><RGB8><red>249</red><green>168</green><blue>0</blue></RGB8></colorEntry>
|
||||
<colorEntry><colorName>RAL 1004</colorName><RGB8><red>228</red><green>158</green><blue>0</blue></RGB8></colorEntry>
|
||||
<colorEntry><colorName>RAL 1005</colorName><RGB8><red>203</red><green>142</green><blue>0</blue></RGB8></colorEntry>
|
||||
<colorEntry><colorName>RAL 1006</colorName><RGB8><red>226</red><green>144</green><blue>0</blue></RGB8></colorEntry>
|
||||
</colorPage>
|
||||
<colorPage>
|
||||
<colorEntry><colorName>RAL 1007</colorName><RGB8><red>232</red><green>140</green><blue>0</blue></RGB8></colorEntry>
|
||||
<colorEntry><colorName>RAL 1011</colorName><RGB8><red>175</red><green>128</green><blue>79</blue></RGB8></colorEntry>
|
||||
<colorEntry><colorName>RAL 1012</colorName><RGB8><red>221</red><green>175</green><blue>39</blue></RGB8></colorEntry>
|
||||
<colorEntry><colorName>RAL 1013</colorName><RGB8><red>227</red><green>217</green><blue>198</blue></RGB8></colorEntry>
|
||||
<colorEntry><colorName>RAL 1014</colorName><RGB8><red>221</red><green>196</green><blue>154</blue></RGB8></colorEntry>
|
||||
<colorEntry><colorName>RAL 1015</colorName><RGB8><red>230</red><green>210</green><blue>181</blue></RGB8></colorEntry>
|
||||
<colorEntry><colorName>RAL 1016</colorName><RGB8><red>241</red><green>221</green><blue>56</blue></RGB8></colorEntry>
|
||||
</colorPage>
|
||||
<colorPage>
|
||||
<colorEntry><colorName>RAL 1017</colorName><RGB8><red>246</red><green>169</green><blue>80</blue></RGB8></colorEntry>
|
||||
<colorEntry><colorName>RAL 1018</colorName><RGB8><red>250</red><green>202</green><blue>48</blue></RGB8></colorEntry>
|
||||
<colorEntry><colorName>RAL 1019</colorName><RGB8><red>164</red><green>143</green><blue>122</blue></RGB8></colorEntry>
|
||||
<colorEntry><colorName>RAL 1020</colorName><RGB8><red>160</red><green>143</green><blue>101</blue></RGB8></colorEntry>
|
||||
<colorEntry><colorName>RAL 1021</colorName><RGB8><red>246</red><green>182</green><blue>0</blue></RGB8></colorEntry>
|
||||
<colorEntry><colorName>RAL 1023</colorName><RGB8><red>247</red><green>181</green><blue>0</blue></RGB8></colorEntry>
|
||||
<colorEntry><colorName>RAL 1024</colorName><RGB8><red>186</red><green>143</green><blue>76</blue></RGB8></colorEntry>
|
||||
</colorPage>
|
||||
<colorPage>
|
||||
<colorEntry><colorName>RAL 1026</colorName><RGB8><red>255</red><green>255</green><blue>0</blue></RGB8></colorEntry>
|
||||
<colorEntry><colorName>RAL 1027</colorName><RGB8><red>167</red><green>127</green><blue>14</blue></RGB8></colorEntry>
|
||||
<colorEntry><colorName>RAL 1028</colorName><RGB8><red>255</red><green>155</green><blue>0</blue></RGB8></colorEntry>
|
||||
<colorEntry><colorName>RAL 1032</colorName><RGB8><red>226</red><green>163</green><blue>0</blue></RGB8></colorEntry>
|
||||
<colorEntry><colorName>RAL 1033</colorName><RGB8><red>249</red><green>154</green><blue>28</blue></RGB8></colorEntry>
|
||||
<colorEntry><colorName>RAL 1034</colorName><RGB8><red>235</red><green>156</green><blue>82</blue></RGB8></colorEntry>
|
||||
<colorEntry><colorName>RAL 1035</colorName><RGB8><red>144</red><green>131</green><blue>112</blue></RGB8></colorEntry>
|
||||
</colorPage>
|
||||
<colorPage>
|
||||
<colorEntry><colorName>RAL 1036</colorName><RGB8><red>128</red><green>100</green><blue>63</blue></RGB8></colorEntry>
|
||||
<colorEntry><colorName>RAL 1037</colorName><RGB8><red>240</red><green>146</green><blue>0</blue></RGB8></colorEntry>
|
||||
</colorPage>
|
||||
<colorPage>
|
||||
<colorEntry><colorName>RAL 2000</colorName><RGB8><red>221</red><green>121</green><blue>7</blue></RGB8></colorEntry>
|
||||
<colorEntry><colorName>RAL 2001</colorName><RGB8><red>190</red><green>78</green><blue>32</blue></RGB8></colorEntry>
|
||||
<colorEntry><colorName>RAL 2002</colorName><RGB8><red>198</red><green>57</green><blue>39</blue></RGB8></colorEntry>
|
||||
<colorEntry><colorName>RAL 2003</colorName><RGB8><red>250</red><green>132</green><blue>43</blue></RGB8></colorEntry>
|
||||
<colorEntry><colorName>RAL 2004</colorName><RGB8><red>231</red><green>91</green><blue>18</blue></RGB8></colorEntry>
|
||||
<colorEntry><colorName>RAL 2005</colorName><RGB8><red>255</red><green>35</green><blue>0</blue></RGB8></colorEntry>
|
||||
<colorEntry><colorName>RAL 2007</colorName><RGB8><red>255</red><green>164</green><blue>33</blue></RGB8></colorEntry>
|
||||
</colorPage>
|
||||
<colorPage>
|
||||
<colorEntry><colorName>RAL 2008</colorName><RGB8><red>243</red><green>117</green><blue>44</blue></RGB8></colorEntry>
|
||||
<colorEntry><colorName>RAL 2009</colorName><RGB8><red>225</red><green>85</green><blue>1</blue></RGB8></colorEntry>
|
||||
<colorEntry><colorName>RAL 2010</colorName><RGB8><red>212</red><green>101</green><blue>47</blue></RGB8></colorEntry>
|
||||
<colorEntry><colorName>RAL 2011</colorName><RGB8><red>236</red><green>124</green><blue>37</blue></RGB8></colorEntry>
|
||||
<colorEntry><colorName>RAL 2012</colorName><RGB8><red>219</red><green>106</green><blue>80</blue></RGB8></colorEntry>
|
||||
<colorEntry><colorName>RAL 2013</colorName><RGB8><red>149</red><green>69</green><blue>39</blue></RGB8></colorEntry>
|
||||
<colorEntry><colorName>RAL 2017</colorName><RGB8><red>250</red><green>68</green><blue>2</blue></RGB8></colorEntry>
|
||||
</colorPage>
|
||||
<colorPage>
|
||||
<colorEntry><colorName>RAL 3000</colorName><RGB8><red>171</red><green>37</green><blue>36</blue></RGB8></colorEntry>
|
||||
<colorEntry><colorName>RAL 3001</colorName><RGB8><red>160</red><green>33</green><blue>40</blue></RGB8></colorEntry>
|
||||
<colorEntry><colorName>RAL 3002</colorName><RGB8><red>161</red><green>35</green><blue>43</blue></RGB8></colorEntry>
|
||||
<colorEntry><colorName>RAL 3003</colorName><RGB8><red>141</red><green>29</green><blue>44</blue></RGB8></colorEntry>
|
||||
<colorEntry><colorName>RAL 3004</colorName><RGB8><red>112</red><green>31</green><blue>41</blue></RGB8></colorEntry>
|
||||
<colorEntry><colorName>RAL 3005</colorName><RGB8><red>94</red><green>32</green><blue>40</blue></RGB8></colorEntry>
|
||||
<colorEntry><colorName>RAL 3007</colorName><RGB8><red>64</red><green>34</green><blue>37</blue></RGB8></colorEntry>
|
||||
</colorPage>
|
||||
<colorPage>
|
||||
<colorEntry><colorName>RAL 3009</colorName><RGB8><red>112</red><green>55</green><blue>49</blue></RGB8></colorEntry>
|
||||
<colorEntry><colorName>RAL 3011</colorName><RGB8><red>126</red><green>41</green><blue>44</blue></RGB8></colorEntry>
|
||||
<colorEntry><colorName>RAL 3012</colorName><RGB8><red>203</red><green>141</green><blue>115</blue></RGB8></colorEntry>
|
||||
<colorEntry><colorName>RAL 3013</colorName><RGB8><red>156</red><green>50</green><blue>46</blue></RGB8></colorEntry>
|
||||
<colorEntry><colorName>RAL 3014</colorName><RGB8><red>212</red><green>116</green><blue>121</blue></RGB8></colorEntry>
|
||||
<colorEntry><colorName>RAL 3015</colorName><RGB8><red>225</red><green>166</green><blue>173</blue></RGB8></colorEntry>
|
||||
<colorEntry><colorName>RAL 3016</colorName><RGB8><red>172</red><green>64</green><blue>52</blue></RGB8></colorEntry>
|
||||
</colorPage>
|
||||
<colorPage>
|
||||
<colorEntry><colorName>RAL 3017</colorName><RGB8><red>211</red><green>84</green><blue>95</blue></RGB8></colorEntry>
|
||||
<colorEntry><colorName>RAL 3018</colorName><RGB8><red>209</red><green>65</green><blue>82</blue></RGB8></colorEntry>
|
||||
<colorEntry><colorName>RAL 3020</colorName><RGB8><red>193</red><green>18</green><blue>28</blue></RGB8></colorEntry>
|
||||
<colorEntry><colorName>RAL 3022</colorName><RGB8><red>213</red><green>109</green><blue>86</blue></RGB8></colorEntry>
|
||||
<colorEntry><colorName>RAL 3024</colorName><RGB8><red>247</red><green>0</green><blue>0</blue></RGB8></colorEntry>
|
||||
<colorEntry><colorName>RAL 3026</colorName><RGB8><red>255</red><green>0</green><blue>0</blue></RGB8></colorEntry>
|
||||
<colorEntry><colorName>RAL 3027</colorName><RGB8><red>180</red><green>32</green><blue>65</blue></RGB8></colorEntry>
|
||||
</colorPage>
|
||||
<colorPage>
|
||||
<colorEntry><colorName>RAL 3028</colorName><RGB8><red>231</red><green>37</green><blue>18</blue></RGB8></colorEntry>
|
||||
<colorEntry><colorName>RAL 3031</colorName><RGB8><red>172</red><green>50</green><blue>59</blue></RGB8></colorEntry>
|
||||
<colorEntry><colorName>RAL 3032</colorName><RGB8><red>113</red><green>21</green><blue>33</blue></RGB8></colorEntry>
|
||||
<colorEntry><colorName>RAL 3033</colorName><RGB8><red>178</red><green>76</green><blue>67</blue></RGB8></colorEntry>
|
||||
</colorPage>
|
||||
<colorPage>
|
||||
<colorEntry><colorName>RAL 4001</colorName><RGB8><red>138</red><green>90</green><blue>131</blue></RGB8></colorEntry>
|
||||
<colorEntry><colorName>RAL 4002</colorName><RGB8><red>147</red><green>61</green><blue>80</blue></RGB8></colorEntry>
|
||||
<colorEntry><colorName>RAL 4003</colorName><RGB8><red>209</red><green>91</green><blue>143</blue></RGB8></colorEntry>
|
||||
<colorEntry><colorName>RAL 4004</colorName><RGB8><red>105</red><green>22</green><blue>57</blue></RGB8></colorEntry>
|
||||
<colorEntry><colorName>RAL 4005</colorName><RGB8><red>131</red><green>99</green><blue>157</blue></RGB8></colorEntry>
|
||||
<colorEntry><colorName>RAL 4006</colorName><RGB8><red>153</red><green>37</green><blue>114</blue></RGB8></colorEntry>
|
||||
<colorEntry><colorName>RAL 4007</colorName><RGB8><red>74</red><green>32</green><blue>59</blue></RGB8></colorEntry>
|
||||
</colorPage>
|
||||
<colorPage>
|
||||
<colorEntry><colorName>RAL 4008</colorName><RGB8><red>144</red><green>70</green><blue>132</blue></RGB8></colorEntry>
|
||||
<colorEntry><colorName>RAL 4009</colorName><RGB8><red>163</red><green>137</green><blue>149</blue></RGB8></colorEntry>
|
||||
<colorEntry><colorName>RAL 4010</colorName><RGB8><red>198</red><green>54</green><blue>120</blue></RGB8></colorEntry>
|
||||
<colorEntry><colorName>RAL 4011</colorName><RGB8><red>135</red><green>115</green><blue>161</blue></RGB8></colorEntry>
|
||||
<colorEntry><colorName>RAL 4012</colorName><RGB8><red>107</red><green>104</green><blue>128</blue></RGB8></colorEntry>
|
||||
</colorPage>
|
||||
<colorPage>
|
||||
<colorEntry><colorName>RAL 5000</colorName><RGB8><red>56</red><green>76</green><blue>112</blue></RGB8></colorEntry>
|
||||
<colorEntry><colorName>RAL 5001</colorName><RGB8><red>31</red><green>71</green><blue>100</blue></RGB8></colorEntry>
|
||||
<colorEntry><colorName>RAL 5002</colorName><RGB8><red>43</red><green>44</green><blue>124</blue></RGB8></colorEntry>
|
||||
<colorEntry><colorName>RAL 5003</colorName><RGB8><red>42</red><green>55</green><blue>86</blue></RGB8></colorEntry>
|
||||
<colorEntry><colorName>RAL 5004</colorName><RGB8><red>29</red><green>31</green><blue>42</blue></RGB8></colorEntry>
|
||||
<colorEntry><colorName>RAL 5005</colorName><RGB8><red>21</red><green>72</green><blue>137</blue></RGB8></colorEntry>
|
||||
<colorEntry><colorName>RAL 5007</colorName><RGB8><red>65</red><green>103</green><blue>141</blue></RGB8></colorEntry>
|
||||
</colorPage>
|
||||
<colorPage>
|
||||
<colorEntry><colorName>RAL 5008</colorName><RGB8><red>49</red><green>60</green><blue>72</blue></RGB8></colorEntry>
|
||||
<colorEntry><colorName>RAL 5009</colorName><RGB8><red>46</red><green>89</green><blue>120</blue></RGB8></colorEntry>
|
||||
<colorEntry><colorName>RAL 5010</colorName><RGB8><red>19</red><green>68</green><blue>124</blue></RGB8></colorEntry>
|
||||
<colorEntry><colorName>RAL 5011</colorName><RGB8><red>35</red><green>44</green><blue>63</blue></RGB8></colorEntry>
|
||||
<colorEntry><colorName>RAL 5012</colorName><RGB8><red>52</red><green>129</green><blue>184</blue></RGB8></colorEntry>
|
||||
<colorEntry><colorName>RAL 5013</colorName><RGB8><red>35</red><green>45</green><blue>83</blue></RGB8></colorEntry>
|
||||
<colorEntry><colorName>RAL 5014</colorName><RGB8><red>108</red><green>124</green><blue>152</blue></RGB8></colorEntry>
|
||||
</colorPage>
|
||||
<colorPage>
|
||||
<colorEntry><colorName>RAL 5015</colorName><RGB8><red>40</red><green>116</green><blue>178</blue></RGB8></colorEntry>
|
||||
<colorEntry><colorName>RAL 5017</colorName><RGB8><red>14</red><green>81</green><blue>141</blue></RGB8></colorEntry>
|
||||
<colorEntry><colorName>RAL 5018</colorName><RGB8><red>33</red><green>136</green><blue>143</blue></RGB8></colorEntry>
|
||||
<colorEntry><colorName>RAL 5019</colorName><RGB8><red>26</red><green>87</green><blue>132</blue></RGB8></colorEntry>
|
||||
<colorEntry><colorName>RAL 5020</colorName><RGB8><red>11</red><green>65</green><blue>81</blue></RGB8></colorEntry>
|
||||
<colorEntry><colorName>RAL 5021</colorName><RGB8><red>7</red><green>115</green><blue>122</blue></RGB8></colorEntry>
|
||||
<colorEntry><colorName>RAL 5022</colorName><RGB8><red>47</red><green>42</green><blue>90</blue></RGB8></colorEntry>
|
||||
</colorPage>
|
||||
<colorPage>
|
||||
<colorEntry><colorName>RAL 5023</colorName><RGB8><red>77</red><green>102</green><blue>142</blue></RGB8></colorEntry>
|
||||
<colorEntry><colorName>RAL 5024</colorName><RGB8><red>106</red><green>147</green><blue>176</blue></RGB8></colorEntry>
|
||||
<colorEntry><colorName>RAL 5025</colorName><RGB8><red>41</red><green>100</green><blue>120</blue></RGB8></colorEntry>
|
||||
<colorEntry><colorName>RAL 5026</colorName><RGB8><red>16</red><green>44</green><blue>84</blue></RGB8></colorEntry>
|
||||
</colorPage>
|
||||
<colorPage>
|
||||
<colorEntry><colorName>RAL 6000</colorName><RGB8><red>50</red><green>118</green><blue>98</blue></RGB8></colorEntry>
|
||||
<colorEntry><colorName>RAL 6001</colorName><RGB8><red>40</red><green>113</green><blue>62</blue></RGB8></colorEntry>
|
||||
<colorEntry><colorName>RAL 6002</colorName><RGB8><red>39</red><green>98</green><blue>53</blue></RGB8></colorEntry>
|
||||
<colorEntry><colorName>RAL 6003</colorName><RGB8><red>75</red><green>87</green><blue>62</blue></RGB8></colorEntry>
|
||||
<colorEntry><colorName>RAL 6004</colorName><RGB8><red>14</red><green>66</green><blue>67</blue></RGB8></colorEntry>
|
||||
<colorEntry><colorName>RAL 6005</colorName><RGB8><red>15</red><green>67</green><blue>54</blue></RGB8></colorEntry>
|
||||
<colorEntry><colorName>RAL 6006</colorName><RGB8><red>64</red><green>67</green><blue>59</blue></RGB8></colorEntry>
|
||||
</colorPage>
|
||||
<colorPage>
|
||||
<colorEntry><colorName>RAL 6007</colorName><RGB8><red>40</red><green>52</green><blue>36</blue></RGB8></colorEntry>
|
||||
<colorEntry><colorName>RAL 6008</colorName><RGB8><red>53</red><green>56</green><blue>46</blue></RGB8></colorEntry>
|
||||
<colorEntry><colorName>RAL 6009</colorName><RGB8><red>38</red><green>57</green><blue>47</blue></RGB8></colorEntry>
|
||||
<colorEntry><colorName>RAL 6010</colorName><RGB8><red>62</red><green>117</green><blue>59</blue></RGB8></colorEntry>
|
||||
<colorEntry><colorName>RAL 6011</colorName><RGB8><red>104</red><green>130</green><blue>91</blue></RGB8></colorEntry>
|
||||
<colorEntry><colorName>RAL 6012</colorName><RGB8><red>49</red><green>64</green><blue>61</blue></RGB8></colorEntry>
|
||||
<colorEntry><colorName>RAL 6013</colorName><RGB8><red>121</red><green>124</green><blue>90</blue></RGB8></colorEntry>
|
||||
</colorPage>
|
||||
<colorPage>
|
||||
<colorEntry><colorName>RAL 6014</colorName><RGB8><red>68</red><green>67</green><blue>55</blue></RGB8></colorEntry>
|
||||
<colorEntry><colorName>RAL 6015</colorName><RGB8><red>61</red><green>64</green><blue>58</blue></RGB8></colorEntry>
|
||||
<colorEntry><colorName>RAL 6016</colorName><RGB8><red>2</red><green>106</green><blue>82</blue></RGB8></colorEntry>
|
||||
<colorEntry><colorName>RAL 6017</colorName><RGB8><red>70</red><green>134</green><blue>65</blue></RGB8></colorEntry>
|
||||
<colorEntry><colorName>RAL 6018</colorName><RGB8><red>72</red><green>164</green><blue>63</blue></RGB8></colorEntry>
|
||||
<colorEntry><colorName>RAL 6019</colorName><RGB8><red>183</red><green>217</green><blue>177</blue></RGB8></colorEntry>
|
||||
<colorEntry><colorName>RAL 6020</colorName><RGB8><red>53</red><green>71</green><blue>51</blue></RGB8></colorEntry>
|
||||
</colorPage>
|
||||
<colorPage>
|
||||
<colorEntry><colorName>RAL 6021</colorName><RGB8><red>134</red><green>164</green><blue>124</blue></RGB8></colorEntry>
|
||||
<colorEntry><colorName>RAL 6022</colorName><RGB8><red>62</red><green>60</green><blue>50</blue></RGB8></colorEntry>
|
||||
<colorEntry><colorName>RAL 6024</colorName><RGB8><red>0</red><green>135</green><blue>84</blue></RGB8></colorEntry>
|
||||
<colorEntry><colorName>RAL 6025</colorName><RGB8><red>83</red><green>117</green><blue>60</blue></RGB8></colorEntry>
|
||||
<colorEntry><colorName>RAL 6026</colorName><RGB8><red>0</red><green>93</green><blue>82</blue></RGB8></colorEntry>
|
||||
<colorEntry><colorName>RAL 6027</colorName><RGB8><red>129</red><green>192</green><blue>187</blue></RGB8></colorEntry>
|
||||
<colorEntry><colorName>RAL 6028</colorName><RGB8><red>45</red><green>85</green><blue>70</blue></RGB8></colorEntry>
|
||||
</colorPage>
|
||||
<colorPage>
|
||||
<colorEntry><colorName>RAL 6029</colorName><RGB8><red>0</red><green>114</green><blue>67</blue></RGB8></colorEntry>
|
||||
<colorEntry><colorName>RAL 6032</colorName><RGB8><red>15</red><green>133</green><blue>88</blue></RGB8></colorEntry>
|
||||
<colorEntry><colorName>RAL 6033</colorName><RGB8><red>71</red><green>138</green><blue>132</blue></RGB8></colorEntry>
|
||||
<colorEntry><colorName>RAL 6034</colorName><RGB8><red>127</red><green>176</green><blue>178</blue></RGB8></colorEntry>
|
||||
<colorEntry><colorName>RAL 6035</colorName><RGB8><red>27</red><green>84</green><blue>44</blue></RGB8></colorEntry>
|
||||
<colorEntry><colorName>RAL 6036</colorName><RGB8><red>0</red><green>93</green><blue>76</blue></RGB8></colorEntry>
|
||||
<colorEntry><colorName>RAL 6037</colorName><RGB8><red>37</red><green>231</green><blue>18</blue></RGB8></colorEntry>
|
||||
</colorPage>
|
||||
<colorPage>
|
||||
<colorEntry><colorName>RAL 6038</colorName><RGB8><red>0</red><green>247</green><blue>0</blue></RGB8></colorEntry>
|
||||
<colorEntry><colorName>RAL 6039</colorName><RGB8><red>179</red><green>196</green><blue>62</blue></RGB8></colorEntry>
|
||||
</colorPage>
|
||||
<colorPage>
|
||||
<colorEntry><colorName>RAL 7000</colorName><RGB8><red>126</red><green>139</green><blue>146</blue></RGB8></colorEntry>
|
||||
<colorEntry><colorName>RAL 7001</colorName><RGB8><red>143</red><green>153</green><blue>159</blue></RGB8></colorEntry>
|
||||
<colorEntry><colorName>RAL 7002</colorName><RGB8><red>129</red><green>127</green><blue>104</blue></RGB8></colorEntry>
|
||||
<colorEntry><colorName>RAL 7003</colorName><RGB8><red>122</red><green>123</green><blue>109</blue></RGB8></colorEntry>
|
||||
<colorEntry><colorName>RAL 7004</colorName><RGB8><red>158</red><green>160</green><blue>161</blue></RGB8></colorEntry>
|
||||
<colorEntry><colorName>RAL 7005</colorName><RGB8><red>107</red><green>113</green><blue>111</blue></RGB8></colorEntry>
|
||||
<colorEntry><colorName>RAL 7006</colorName><RGB8><red>117</red><green>111</green><blue>97</blue></RGB8></colorEntry>
|
||||
</colorPage>
|
||||
<colorPage>
|
||||
<colorEntry><colorName>RAL 7008</colorName><RGB8><red>116</red><green>102</green><blue>67</blue></RGB8></colorEntry>
|
||||
<colorEntry><colorName>RAL 7009</colorName><RGB8><red>91</red><green>98</green><blue>89</blue></RGB8></colorEntry>
|
||||
<colorEntry><colorName>RAL 7010</colorName><RGB8><red>87</red><green>93</green><blue>87</blue></RGB8></colorEntry>
|
||||
<colorEntry><colorName>RAL 7011</colorName><RGB8><red>85</red><green>93</green><blue>97</blue></RGB8></colorEntry>
|
||||
<colorEntry><colorName>RAL 7012</colorName><RGB8><red>89</red><green>97</green><blue>99</blue></RGB8></colorEntry>
|
||||
<colorEntry><colorName>RAL 7013</colorName><RGB8><red>85</red><green>85</green><blue>72</blue></RGB8></colorEntry>
|
||||
<colorEntry><colorName>RAL 7015</colorName><RGB8><red>81</red><green>86</green><blue>92</blue></RGB8></colorEntry>
|
||||
</colorPage>
|
||||
<colorPage>
|
||||
<colorEntry><colorName>RAL 7016</colorName><RGB8><red>55</red><green>63</green><blue>67</blue></RGB8></colorEntry>
|
||||
<colorEntry><colorName>RAL 7021</colorName><RGB8><red>46</red><green>50</green><blue>52</blue></RGB8></colorEntry>
|
||||
<colorEntry><colorName>RAL 7022</colorName><RGB8><red>75</red><green>77</green><blue>70</blue></RGB8></colorEntry>
|
||||
<colorEntry><colorName>RAL 7023</colorName><RGB8><red>129</red><green>132</green><blue>121</blue></RGB8></colorEntry>
|
||||
<colorEntry><colorName>RAL 7024</colorName><RGB8><red>71</red><green>74</green><blue>80</blue></RGB8></colorEntry>
|
||||
<colorEntry><colorName>RAL 7026</colorName><RGB8><red>55</red><green>68</green><blue>71</blue></RGB8></colorEntry>
|
||||
<colorEntry><colorName>RAL 7030</colorName><RGB8><red>147</red><green>147</green><blue>136</blue></RGB8></colorEntry>
|
||||
</colorPage>
|
||||
<colorPage>
|
||||
<colorEntry><colorName>RAL 7031</colorName><RGB8><red>93</red><green>105</green><blue>112</blue></RGB8></colorEntry>
|
||||
<colorEntry><colorName>RAL 7032</colorName><RGB8><red>185</red><green>185</green><blue>168</blue></RGB8></colorEntry>
|
||||
<colorEntry><colorName>RAL 7033</colorName><RGB8><red>129</red><green>137</green><blue>121</blue></RGB8></colorEntry>
|
||||
<colorEntry><colorName>RAL 7034</colorName><RGB8><red>147</red><green>145</green><blue>118</blue></RGB8></colorEntry>
|
||||
<colorEntry><colorName>RAL 7035</colorName><RGB8><red>203</red><green>208</green><blue>204</blue></RGB8></colorEntry>
|
||||
<colorEntry><colorName>RAL 7036</colorName><RGB8><red>154</red><green>150</green><blue>151</blue></RGB8></colorEntry>
|
||||
<colorEntry><colorName>RAL 7037</colorName><RGB8><red>124</red><green>127</green><blue>126</blue></RGB8></colorEntry>
|
||||
</colorPage>
|
||||
<colorPage>
|
||||
<colorEntry><colorName>RAL 7038</colorName><RGB8><red>180</red><green>184</green><blue>176</blue></RGB8></colorEntry>
|
||||
<colorEntry><colorName>RAL 7039</colorName><RGB8><red>107</red><green>105</green><blue>95</blue></RGB8></colorEntry>
|
||||
<colorEntry><colorName>RAL 7040</colorName><RGB8><red>157</red><green>163</green><blue>166</blue></RGB8></colorEntry>
|
||||
<colorEntry><colorName>RAL 7042</colorName><RGB8><red>143</red><green>150</green><blue>149</blue></RGB8></colorEntry>
|
||||
<colorEntry><colorName>RAL 7043</colorName><RGB8><red>78</red><green>84</green><blue>81</blue></RGB8></colorEntry>
|
||||
<colorEntry><colorName>RAL 7044</colorName><RGB8><red>189</red><green>189</green><blue>178</blue></RGB8></colorEntry>
|
||||
<colorEntry><colorName>RAL 7045</colorName><RGB8><red>145</red><green>150</green><blue>154</blue></RGB8></colorEntry>
|
||||
</colorPage>
|
||||
<colorPage>
|
||||
<colorEntry><colorName>RAL 7046</colorName><RGB8><red>130</red><green>137</green><blue>142</blue></RGB8></colorEntry>
|
||||
<colorEntry><colorName>RAL 7047</colorName><RGB8><red>207</red><green>208</green><blue>207</blue></RGB8></colorEntry>
|
||||
<colorEntry><colorName>RAL 7048</colorName><RGB8><red>136</red><green>129</green><blue>117</blue></RGB8></colorEntry>
|
||||
</colorPage>
|
||||
<colorPage>
|
||||
<colorEntry><colorName>RAL 8000</colorName><RGB8><red>136</red><green>113</green><blue>66</blue></RGB8></colorEntry>
|
||||
<colorEntry><colorName>RAL 8001</colorName><RGB8><red>156</red><green>107</green><blue>48</blue></RGB8></colorEntry>
|
||||
<colorEntry><colorName>RAL 8002</colorName><RGB8><red>123</red><green>81</green><blue>65</blue></RGB8></colorEntry>
|
||||
<colorEntry><colorName>RAL 8003</colorName><RGB8><red>128</red><green>84</green><blue>47</blue></RGB8></colorEntry>
|
||||
<colorEntry><colorName>RAL 8004</colorName><RGB8><red>143</red><green>78</green><blue>53</blue></RGB8></colorEntry>
|
||||
<colorEntry><colorName>RAL 8007</colorName><RGB8><red>111</red><green>74</green><blue>47</blue></RGB8></colorEntry>
|
||||
<colorEntry><colorName>RAL 8008</colorName><RGB8><red>111</red><green>79</green><blue>40</blue></RGB8></colorEntry>
|
||||
</colorPage>
|
||||
<colorPage>
|
||||
<colorEntry><colorName>RAL 8011</colorName><RGB8><red>90</red><green>58</green><blue>41</blue></RGB8></colorEntry>
|
||||
<colorEntry><colorName>RAL 8012</colorName><RGB8><red>103</red><green>56</green><blue>49</blue></RGB8></colorEntry>
|
||||
<colorEntry><colorName>RAL 8014</colorName><RGB8><red>73</red><green>57</green><blue>45</blue></RGB8></colorEntry>
|
||||
<colorEntry><colorName>RAL 8015</colorName><RGB8><red>99</red><green>58</green><blue>52</blue></RGB8></colorEntry>
|
||||
<colorEntry><colorName>RAL 8016</colorName><RGB8><red>76</red><green>47</green><blue>38</blue></RGB8></colorEntry>
|
||||
<colorEntry><colorName>RAL 8017</colorName><RGB8><red>68</red><green>50</green><blue>45</blue></RGB8></colorEntry>
|
||||
<colorEntry><colorName>RAL 8019</colorName><RGB8><red>63</red><green>58</green><blue>58</blue></RGB8></colorEntry>
|
||||
</colorPage>
|
||||
<colorPage>
|
||||
<colorEntry><colorName>RAL 8022</colorName><RGB8><red>33</red><green>31</green><blue>32</blue></RGB8></colorEntry>
|
||||
<colorEntry><colorName>RAL 8023</colorName><RGB8><red>166</red><green>94</green><blue>47</blue></RGB8></colorEntry>
|
||||
<colorEntry><colorName>RAL 8024</colorName><RGB8><red>121</red><green>85</green><blue>60</blue></RGB8></colorEntry>
|
||||
<colorEntry><colorName>RAL 8025</colorName><RGB8><red>117</red><green>92</green><blue>73</blue></RGB8></colorEntry>
|
||||
<colorEntry><colorName>RAL 8028</colorName><RGB8><red>78</red><green>59</green><blue>43</blue></RGB8></colorEntry>
|
||||
<colorEntry><colorName>RAL 8029</colorName><RGB8><red>119</red><green>60</green><blue>39</blue></RGB8></colorEntry>
|
||||
</colorPage>
|
||||
<colorPage>
|
||||
<colorEntry><colorName>RAL 9001</colorName><RGB8><red>239</red><green>235</green><blue>220</blue></RGB8></colorEntry>
|
||||
<colorEntry><colorName>RAL 9002</colorName><RGB8><red>221</red><green>222</green><blue>212</blue></RGB8></colorEntry>
|
||||
<colorEntry><colorName>RAL 9003</colorName><RGB8><red>255</red><green>255</green><blue>255</blue></RGB8></colorEntry>
|
||||
<colorEntry><colorName>RAL 9004</colorName><RGB8><red>46</red><green>48</green><blue>50</blue></RGB8></colorEntry>
|
||||
<colorEntry><colorName>RAL 9005</colorName><RGB8><red>19</red><green>21</green><blue>22</blue></RGB8></colorEntry>
|
||||
<colorEntry><colorName>RAL 9006</colorName><RGB8><red>165</red><green>168</green><blue>166</blue></RGB8></colorEntry>
|
||||
<colorEntry><colorName>RAL 9007</colorName><RGB8><red>143</red><green>143</green><blue>140</blue></RGB8></colorEntry>
|
||||
</colorPage>
|
||||
<colorPage>
|
||||
<colorEntry><colorName>RAL 9010</colorName><RGB8><red>247</red><green>249</green><blue>239</blue></RGB8></colorEntry>
|
||||
<colorEntry><colorName>RAL 9011</colorName><RGB8><red>28</red><green>28</green><blue>28</blue></RGB8></colorEntry>
|
||||
<colorEntry><colorName>RAL 9012</colorName><RGB8><red>255</red><green>253</green><blue>230</blue></RGB8></colorEntry>
|
||||
<colorEntry><colorName>RAL 9016</colorName><RGB8><red>247</red><green>251</green><blue>245</blue></RGB8></colorEntry>
|
||||
<colorEntry><colorName>RAL 9017</colorName><RGB8><red>42</red><green>45</green><blue>47</blue></RGB8></colorEntry>
|
||||
<colorEntry><colorName>RAL 9018</colorName><RGB8><red>207</red><green>211</green><blue>205</blue></RGB8></colorEntry>
|
||||
<colorEntry><colorName>RAL 9022</colorName><RGB8><red>133</red><green>133</green><blue>131</blue></RGB8></colorEntry>
|
||||
</colorPage>
|
||||
<colorPage>
|
||||
<colorEntry><colorName>RAL 9023</colorName><RGB8><red>126</red><green>129</green><blue>130</blue></RGB8></colorEntry>
|
||||
</colorPage>
|
||||
</colorBook>
|
||||
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2023 Phosphor Icons
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -0,0 +1,2 @@
|
||||
<!-- Phosphor Icons "eyedropper" (regular), MIT License, Copyright (c) 2023 Phosphor Icons, https://github.com/phosphor-icons/core. See LICENSE-phosphor.txt. -->
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256" fill="#B4B6B9"><path d="M224,67.3a35.79,35.79,0,0,0-11.26-25.66c-14-13.28-36.72-12.78-50.62,1.13L142.8,62.2a24,24,0,0,0-33.14.77l-9,9a16,16,0,0,0,0,22.64l2,2.06-51,51a39.75,39.75,0,0,0-10.53,38l-8,18.41A13.68,13.68,0,0,0,36,219.3a15.92,15.92,0,0,0,17.71,3.35L71.23,215a39.89,39.89,0,0,0,37.06-10.75l51-51,2.06,2.06a16,16,0,0,0,22.62,0l9-9a24,24,0,0,0,.74-33.18l19.75-19.87A35.75,35.75,0,0,0,224,67.3ZM97,193a24,24,0,0,1-24,6,8,8,0,0,0-5.55.31l-18.1,7.91L57,189.41a8,8,0,0,0,.25-5.75A23.88,23.88,0,0,1,63,159l51-51,33.94,34ZM202.13,82l-25.37,25.52a8,8,0,0,0,0,11.3l4.89,4.89a8,8,0,0,1,0,11.32l-9,9L112,83.26l9-9a8,8,0,0,1,11.31,0l4.89,4.89a8,8,0,0,0,11.33,0l24.94-25.09c7.81-7.82,20.5-8.18,28.29-.81a20,20,0,0,1,.39,28.7Z"/></svg>
|
||||
|
After Width: | Height: | Size: 954 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="#B4B6B9" stroke-width="1.8" stroke-linejoin="round"><rect x="4" y="4" width="16" height="4" rx="1"/><rect x="4" y="10" width="16" height="4" rx="1"/><rect x="4" y="16" width="16" height="4" rx="1"/></svg>
|
||||
|
After Width: | Height: | Size: 285 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="#B4B6B9" stroke-width="2" stroke-linecap="round"><circle cx="10.5" cy="10.5" r="6"/><path d="M15 15l5 5"/></svg>
|
||||
|
After Width: | Height: | Size: 193 B |
@@ -1430,15 +1430,31 @@ common =
|
||||
.dismiss = إغلاق
|
||||
.value-8a8de8 = ?
|
||||
.select-color-8dddf1 = { common.select-color }…
|
||||
.no-recent-colors-yet = لا توجد ألوان حديثة بعد
|
||||
.true-color-rgb = { common.true-color } RGB __ocs_fmt_0__, __ocs_fmt_1__, __ocs_fmt_2__
|
||||
.index-color = { common.color } (ACI)
|
||||
.true-color = { common.color } (RGB)
|
||||
.standard-colors = { common.standard } · { common.color }
|
||||
.color-hex = سداسي عشري
|
||||
.nearest-index-color = أقرب فهرس
|
||||
.exact-index-match = تطابق تام مع الفهرس
|
||||
.color-recently-used = المستخدمة مؤخرًا
|
||||
.color-most-used = الأكثر استخدامًا
|
||||
.color-number = رقم اللون
|
||||
.color-name-az = الاسم أ–ي
|
||||
.color-name-za = الاسم ي–أ
|
||||
.color-sort = فرز
|
||||
.color-no-drawing-colors = لا توجد ألوان في هذا الرسم بعد
|
||||
.color-books = كتب الألوان
|
||||
.color-no-books = لم يتم تحميل كتب ألوان
|
||||
.color-load-book = تحميل كتاب ألوان (.acb)
|
||||
.color-book-label = كتاب الألوان
|
||||
.color-rgb-equivalent = مكافئ RGB
|
||||
.color-no-match = لا توجد ألوان مطابقة
|
||||
.color-not-a-book = ليس كتاب ألوان
|
||||
.color-book-encrypted = كتاب الألوان هذا مشفر ولا يمكن قراءته
|
||||
.color-book-empty = لا يحتوي كتاب الألوان على ألوان
|
||||
.color-book-locations = مواقع كتب الألوان
|
||||
.cad-color-index-aci-10-249 = فهرس ألوان ACI 10–249
|
||||
.color-family-shade-intensity = { common.color-family } → · { common.shade } / { properties.intensity } ↓
|
||||
.grayscale-250-255 = { common.grayscale } 250–255
|
||||
.recently-used = المستخدمة مؤخرًا
|
||||
.isometric-drafting = الرسم متساوي القياس
|
||||
.f5-cycles-the-active-plane = يبدّل F5 المستوى النشط بالتتابع.
|
||||
.units-5430a1 = { common.units }…
|
||||
|
||||
@@ -1417,15 +1417,31 @@ common =
|
||||
.dismiss = Затваряне
|
||||
.value-8a8de8 = ?
|
||||
.select-color-8dddf1 = { common.select-color }…
|
||||
.no-recent-colors-yet = Все още няма скорошни цветове
|
||||
.true-color-rgb = { common.true-color } RGB __ocs_fmt_0__, __ocs_fmt_1__, __ocs_fmt_2__
|
||||
.index-color = { common.color } (ACI)
|
||||
.true-color = { common.color } (RGB)
|
||||
.standard-colors = { common.standard } · { common.color }
|
||||
.color-hex = Hex
|
||||
.nearest-index-color = Най-близък индекс
|
||||
.exact-index-match = Точно съвпадение с индекс
|
||||
.color-recently-used = Наскоро използвани
|
||||
.color-most-used = Най-често използвани
|
||||
.color-number = Номер на цвят
|
||||
.color-name-az = Име А–Я
|
||||
.color-name-za = Име Я–А
|
||||
.color-sort = Сортиране
|
||||
.color-no-drawing-colors = Все още няма цветове в този чертеж
|
||||
.color-books = Цветови каталози
|
||||
.color-no-books = Няма заредени цветови каталози
|
||||
.color-load-book = Зареждане на цветови каталог (.acb)
|
||||
.color-book-label = Цветови каталог
|
||||
.color-rgb-equivalent = RGB еквивалент
|
||||
.color-no-match = Няма съвпадащи цветове
|
||||
.color-not-a-book = Не е цветови каталог
|
||||
.color-book-encrypted = Този цветови каталог е шифрован и не може да бъде прочетен
|
||||
.color-book-empty = Цветовият каталог няма цветове
|
||||
.color-book-locations = Местоположения на цветови каталози
|
||||
.cad-color-index-aci-10-249 = ACI Цветови индекс 10–249
|
||||
.color-family-shade-intensity = { common.color-family } → · { common.shade } / { properties.intensity } ↓
|
||||
.grayscale-250-255 = { common.grayscale } 250–255
|
||||
.recently-used = Последно използвани
|
||||
.isometric-drafting = Изометрично чертане
|
||||
.f5-cycles-the-active-plane = F5 превключва последователно активната равнина.
|
||||
.units-5430a1 = { common.units }…
|
||||
|
||||
@@ -1417,15 +1417,31 @@ common =
|
||||
.dismiss = Zavřít
|
||||
.value-8a8de8 = ?
|
||||
.select-color-8dddf1 = { common.select-color }…
|
||||
.no-recent-colors-yet = Zatím žádné nedávné barvy
|
||||
.true-color-rgb = { common.true-color } RGB __ocs_fmt_0__, __ocs_fmt_1__, __ocs_fmt_2__
|
||||
.index-color = { common.color } (ACI)
|
||||
.true-color = { common.color } (RGB)
|
||||
.standard-colors = { common.standard } · { common.color }
|
||||
.color-hex = Hex
|
||||
.nearest-index-color = Nejbližší index
|
||||
.exact-index-match = Přesná shoda s indexem
|
||||
.color-recently-used = Naposledy použité
|
||||
.color-most-used = Nejpoužívanější
|
||||
.color-number = Číslo barvy
|
||||
.color-name-az = Název A–Z
|
||||
.color-name-za = Název Z–A
|
||||
.color-sort = Řadit
|
||||
.color-no-drawing-colors = V tomto výkresu zatím nejsou žádné barvy
|
||||
.color-books = Barevné vzorníky
|
||||
.color-no-books = Nejsou načteny žádné barevné vzorníky
|
||||
.color-load-book = Načíst barevný vzorník (.acb)
|
||||
.color-book-label = Barevný vzorník
|
||||
.color-rgb-equivalent = Ekvivalent RGB
|
||||
.color-no-match = Žádné odpovídající barvy
|
||||
.color-not-a-book = Nejde o barevný vzorník
|
||||
.color-book-encrypted = Tento barevný vzorník je zašifrovaný a nelze jej přečíst
|
||||
.color-book-empty = Barevný vzorník neobsahuje žádné barvy
|
||||
.color-book-locations = Umístění barevných vzorníků
|
||||
.cad-color-index-aci-10-249 = Barevný index ACI 10–249
|
||||
.color-family-shade-intensity = { common.color-family } → · { common.shade } / { properties.intensity } ↓
|
||||
.grayscale-250-255 = { common.grayscale } 250–255
|
||||
.recently-used = Nedávno použité
|
||||
.isometric-drafting = Izometrické kreslení
|
||||
.f5-cycles-the-active-plane = F5 cyklicky přepíná aktivní rovinu.
|
||||
.units-5430a1 = { common.units }…
|
||||
|
||||
@@ -1418,15 +1418,31 @@ common =
|
||||
.dismiss = Schließen
|
||||
.value-8a8de8 = ?
|
||||
.select-color-8dddf1 = { common.select-color }…
|
||||
.no-recent-colors-yet = Noch keine zuletzt verwendeten Farben
|
||||
.true-color-rgb = { common.true-color } RGB __ocs_fmt_0__, __ocs_fmt_1__, __ocs_fmt_2__
|
||||
.index-color = { common.color } (ACI)
|
||||
.true-color = { common.color } (RGB)
|
||||
.standard-colors = { common.standard } · { common.color }
|
||||
.color-hex = Hex
|
||||
.nearest-index-color = Nächster Index
|
||||
.exact-index-match = Exakter Index-Treffer
|
||||
.color-recently-used = Zuletzt verwendet
|
||||
.color-most-used = Am häufigsten verwendet
|
||||
.color-number = Farbnummer
|
||||
.color-name-az = Name A–Z
|
||||
.color-name-za = Name Z–A
|
||||
.color-sort = Sortieren
|
||||
.color-no-drawing-colors = Noch keine Farben in dieser Zeichnung
|
||||
.color-books = Farbbücher
|
||||
.color-no-books = Keine Farbbücher geladen
|
||||
.color-load-book = Farbbuch laden (.acb)
|
||||
.color-book-label = Farbbuch
|
||||
.color-rgb-equivalent = RGB-Entsprechung
|
||||
.color-no-match = Keine passenden Farben
|
||||
.color-not-a-book = Kein Farbbuch
|
||||
.color-book-encrypted = Dieses Farbbuch ist verschlüsselt und kann nicht gelesen werden
|
||||
.color-book-empty = Das Farbbuch enthält keine Farben
|
||||
.color-book-locations = Speicherorte für Farbbücher
|
||||
.cad-color-index-aci-10-249 = ACI Farbindex 10–249
|
||||
.color-family-shade-intensity = { common.color-family } → · { common.shade } / { properties.intensity } ↓
|
||||
.grayscale-250-255 = { common.grayscale } 250–255
|
||||
.recently-used = Zuletzt verwendet
|
||||
.isometric-drafting = Isometrisches Zeichnen
|
||||
.f5-cycles-the-active-plane = F5 wechselt die aktive Ebene der Reihe nach.
|
||||
.units-5430a1 = { common.units }…
|
||||
|
||||
@@ -1419,15 +1419,31 @@ common =
|
||||
.dismiss = Κλείσιμο
|
||||
.value-8a8de8 = ?
|
||||
.select-color-8dddf1 = { common.select-color }…
|
||||
.no-recent-colors-yet = Δεν υπάρχουν ακόμη πρόσφατα χρώματα
|
||||
.true-color-rgb = { common.true-color } RGB __ocs_fmt_0__, __ocs_fmt_1__, __ocs_fmt_2__
|
||||
.index-color = { common.color } (ACI)
|
||||
.true-color = { common.color } (RGB)
|
||||
.standard-colors = { common.standard } · { common.color }
|
||||
.color-hex = Hex
|
||||
.nearest-index-color = Πλησιέστερος δείκτης
|
||||
.exact-index-match = Ακριβής αντιστοίχιση δείκτη
|
||||
.color-recently-used = Πρόσφατα χρησιμοποιημένα
|
||||
.color-most-used = Πιο συχνά χρησιμοποιημένα
|
||||
.color-number = Αριθμός χρώματος
|
||||
.color-name-az = Όνομα Α–Ω
|
||||
.color-name-za = Όνομα Ω–Α
|
||||
.color-sort = Ταξινόμηση
|
||||
.color-no-drawing-colors = Δεν υπάρχουν ακόμη χρώματα σε αυτό το σχέδιο
|
||||
.color-books = Βιβλία χρωμάτων
|
||||
.color-no-books = Δεν έχουν φορτωθεί βιβλία χρωμάτων
|
||||
.color-load-book = Φόρτωση βιβλίου χρωμάτων (.acb)
|
||||
.color-book-label = Βιβλίο χρωμάτων
|
||||
.color-rgb-equivalent = Ισοδύναμο RGB
|
||||
.color-no-match = Κανένα χρώμα δεν ταιριάζει
|
||||
.color-not-a-book = Δεν είναι βιβλίο χρωμάτων
|
||||
.color-book-encrypted = Αυτό το βιβλίο χρωμάτων είναι κρυπτογραφημένο και δεν μπορεί να διαβαστεί
|
||||
.color-book-empty = Το βιβλίο χρωμάτων δεν έχει χρώματα
|
||||
.color-book-locations = Θέσεις βιβλίων χρωμάτων
|
||||
.cad-color-index-aci-10-249 = Δείκτης χρώματος ACI 10–249
|
||||
.color-family-shade-intensity = { common.color-family } → · { common.shade } / { properties.intensity } ↓
|
||||
.grayscale-250-255 = { common.grayscale } 250–255
|
||||
.recently-used = Πρόσφατα χρησιμοποιημένα
|
||||
.isometric-drafting = Ισομετρική σχεδίαση
|
||||
.f5-cycles-the-active-plane = Το F5 εναλλάσσει το ενεργό επίπεδο.
|
||||
.units-5430a1 = { common.units }…
|
||||
|
||||
@@ -1418,15 +1418,31 @@ common =
|
||||
.dismiss = Dismiss
|
||||
.value-8a8de8 = ?
|
||||
.select-color-8dddf1 = Select Color...
|
||||
.no-recent-colors-yet = No recent colors yet
|
||||
.true-color-rgb = True Color RGB __ocs_fmt_0__, __ocs_fmt_1__, __ocs_fmt_2__
|
||||
.index-color = Index Color
|
||||
.true-color = True Color
|
||||
.standard-colors = Standard colors
|
||||
.color-hex = Hex
|
||||
.nearest-index-color = Nearest index
|
||||
.exact-index-match = Exact index match
|
||||
.color-recently-used = Recently used
|
||||
.color-most-used = Most used
|
||||
.color-number = Color number
|
||||
.color-name-az = Name A–Z
|
||||
.color-name-za = Name Z–A
|
||||
.color-sort = Sort
|
||||
.color-no-drawing-colors = No colors in this drawing yet
|
||||
.color-books = Color Books
|
||||
.color-no-books = No color books loaded
|
||||
.color-load-book = Load color book (.acb)
|
||||
.color-book-label = Color book
|
||||
.color-rgb-equivalent = RGB equivalent
|
||||
.color-no-match = No colors match
|
||||
.color-not-a-book = Not a color book
|
||||
.color-book-encrypted = This color book is encrypted and cannot be read
|
||||
.color-book-empty = The color book has no colors
|
||||
.color-book-locations = Color book locations
|
||||
.cad-color-index-aci-10-249 = CAD Color Index (ACI) 10–249
|
||||
.color-family-shade-intensity = Color family → · Shade / intensity ↓
|
||||
.grayscale-250-255 = Grayscale 250–255
|
||||
.recently-used = Recently used
|
||||
.isometric-drafting = Isometric drafting
|
||||
.f5-cycles-the-active-plane = F5 cycles the active plane.
|
||||
.units-5430a1 = Units…
|
||||
|
||||
@@ -1418,15 +1418,31 @@ common =
|
||||
.dismiss = Cerrar
|
||||
.value-8a8de8 = ?
|
||||
.select-color-8dddf1 = { common.select-color }…
|
||||
.no-recent-colors-yet = Aún no hay colores recientes
|
||||
.true-color-rgb = { common.true-color } RGB __ocs_fmt_0__, __ocs_fmt_1__, __ocs_fmt_2__
|
||||
.index-color = { common.color } (ACI)
|
||||
.true-color = { common.color } (RGB)
|
||||
.standard-colors = { common.standard } · { common.color }
|
||||
.color-hex = Hex
|
||||
.nearest-index-color = Índice más cercano
|
||||
.exact-index-match = Coincide con un índice
|
||||
.color-recently-used = Usados recientemente
|
||||
.color-most-used = Más usados
|
||||
.color-number = Número de color
|
||||
.color-name-az = Nombre A–Z
|
||||
.color-name-za = Nombre Z–A
|
||||
.color-sort = Ordenar
|
||||
.color-no-drawing-colors = Aún no hay colores en este dibujo
|
||||
.color-books = Libros de colores
|
||||
.color-no-books = No hay libros de colores cargados
|
||||
.color-load-book = Cargar libro de colores (.acb)
|
||||
.color-book-label = Libro de colores
|
||||
.color-rgb-equivalent = Equivalente RGB
|
||||
.color-no-match = Ningún color coincide
|
||||
.color-not-a-book = No es un libro de colores
|
||||
.color-book-encrypted = Este libro de colores está cifrado y no se puede leer
|
||||
.color-book-empty = El libro de colores no tiene colores
|
||||
.color-book-locations = Ubicaciones de libros de colores
|
||||
.cad-color-index-aci-10-249 = Índice de color ACI 10–249
|
||||
.color-family-shade-intensity = { common.color-family } → · { common.shade } / { properties.intensity } ↓
|
||||
.grayscale-250-255 = { common.grayscale } 250–255
|
||||
.recently-used = Usados recientemente
|
||||
.isometric-drafting = Dibujo isométrico
|
||||
.f5-cycles-the-active-plane = F5 alterna el plano activo.
|
||||
.units-5430a1 = { common.units }…
|
||||
|
||||
@@ -1417,15 +1417,31 @@ common =
|
||||
.dismiss = Sulje
|
||||
.value-8a8de8 = ?
|
||||
.select-color-8dddf1 = { common.select-color }…
|
||||
.no-recent-colors-yet = Ei vielä viimeksi käytettyjä värejä
|
||||
.true-color-rgb = { common.true-color } RGB __ocs_fmt_0__, __ocs_fmt_1__, __ocs_fmt_2__
|
||||
.index-color = { common.color } (ACI)
|
||||
.true-color = { common.color } (RGB)
|
||||
.standard-colors = { common.standard } · { common.color }
|
||||
.color-hex = Hex
|
||||
.nearest-index-color = Lähin indeksi
|
||||
.exact-index-match = Täsmälleen indeksiväri
|
||||
.color-recently-used = Viimeksi käytetyt
|
||||
.color-most-used = Eniten käytetyt
|
||||
.color-number = Värin numero
|
||||
.color-name-az = Nimi A–Ö
|
||||
.color-name-za = Nimi Ö–A
|
||||
.color-sort = Lajittele
|
||||
.color-no-drawing-colors = Tässä piirustuksessa ei ole vielä värejä
|
||||
.color-books = Värikirjat
|
||||
.color-no-books = Värikirjoja ei ole ladattu
|
||||
.color-load-book = Lataa värikirja (.acb)
|
||||
.color-book-label = Värikirja
|
||||
.color-rgb-equivalent = RGB-vastine
|
||||
.color-no-match = Ei vastaavia värejä
|
||||
.color-not-a-book = Ei värikirja
|
||||
.color-book-encrypted = Tämä värikirja on salattu eikä sitä voi lukea
|
||||
.color-book-empty = Värikirjassa ei ole värejä
|
||||
.color-book-locations = Värikirjojen sijainnit
|
||||
.cad-color-index-aci-10-249 = ACI väriindeksi 10–249
|
||||
.color-family-shade-intensity = { common.color-family } → · { common.shade } / { properties.intensity } ↓
|
||||
.grayscale-250-255 = { common.grayscale } 250–255
|
||||
.recently-used = Viimeksi käytetyt
|
||||
.isometric-drafting = Isometrinen piirtäminen
|
||||
.f5-cycles-the-active-plane = F5 vaihtaa aktiivista tasoa.
|
||||
.units-5430a1 = { common.units }…
|
||||
|
||||
@@ -1418,15 +1418,31 @@ common =
|
||||
.dismiss = Fermer
|
||||
.value-8a8de8 = ?
|
||||
.select-color-8dddf1 = { common.select-color }…
|
||||
.no-recent-colors-yet = Aucune couleur récente
|
||||
.true-color-rgb = { common.true-color } RGB __ocs_fmt_0__, __ocs_fmt_1__, __ocs_fmt_2__
|
||||
.index-color = { common.color } (ACI)
|
||||
.true-color = { common.color } (RGB)
|
||||
.standard-colors = { common.standard } · { common.color }
|
||||
.color-hex = Hex
|
||||
.nearest-index-color = Index le plus proche
|
||||
.exact-index-match = Correspondance exacte d'index
|
||||
.color-recently-used = Utilisées récemment
|
||||
.color-most-used = Les plus utilisées
|
||||
.color-number = Numéro de couleur
|
||||
.color-name-az = Nom A–Z
|
||||
.color-name-za = Nom Z–A
|
||||
.color-sort = Trier
|
||||
.color-no-drawing-colors = Aucune couleur dans ce dessin pour l'instant
|
||||
.color-books = Carnets de couleurs
|
||||
.color-no-books = Aucun carnet de couleurs chargé
|
||||
.color-load-book = Charger un carnet de couleurs (.acb)
|
||||
.color-book-label = Carnet de couleurs
|
||||
.color-rgb-equivalent = Équivalent RVB
|
||||
.color-no-match = Aucune couleur correspondante
|
||||
.color-not-a-book = Ce n'est pas un carnet de couleurs
|
||||
.color-book-encrypted = Ce carnet de couleurs est chiffré et ne peut pas être lu
|
||||
.color-book-empty = Le carnet de couleurs ne contient aucune couleur
|
||||
.color-book-locations = Emplacements des carnets de couleurs
|
||||
.cad-color-index-aci-10-249 = ACI Indice de couleur 10–249
|
||||
.color-family-shade-intensity = { common.color-family } → · { common.shade } / { properties.intensity } ↓
|
||||
.grayscale-250-255 = { common.grayscale } 250–255
|
||||
.recently-used = Utilisés récemment
|
||||
.isometric-drafting = Dessin isométrique
|
||||
.f5-cycles-the-active-plane = F5 fait défiler les plans actifs.
|
||||
.units-5430a1 = { common.units }…
|
||||
|
||||
@@ -1406,15 +1406,31 @@ common =
|
||||
.dismiss = बंद करें
|
||||
.value-8a8de8 = ?
|
||||
.select-color-8dddf1 = { common.select-color }…
|
||||
.no-recent-colors-yet = अभी कोई हालिया रंग नहीं है
|
||||
.true-color-rgb = { common.true-color } RGB __ocs_fmt_0__, __ocs_fmt_1__, __ocs_fmt_2__
|
||||
.index-color = { common.color } (ACI)
|
||||
.true-color = { common.color } (RGB)
|
||||
.standard-colors = { common.standard } · { common.color }
|
||||
.color-hex = हेक्स
|
||||
.nearest-index-color = निकटतम इंडेक्स
|
||||
.exact-index-match = इंडेक्स से सटीक मेल
|
||||
.color-recently-used = हाल ही में उपयोग किए गए
|
||||
.color-most-used = सबसे अधिक उपयोग किए गए
|
||||
.color-number = रंग संख्या
|
||||
.color-name-az = नाम A–Z
|
||||
.color-name-za = नाम Z–A
|
||||
.color-sort = क्रमबद्ध करें
|
||||
.color-no-drawing-colors = इस ड्रॉइंग में अभी तक कोई रंग नहीं है
|
||||
.color-books = रंग पुस्तकें
|
||||
.color-no-books = कोई रंग पुस्तक लोड नहीं है
|
||||
.color-load-book = रंग पुस्तक लोड करें (.acb)
|
||||
.color-book-label = रंग पुस्तक
|
||||
.color-rgb-equivalent = RGB समतुल्य
|
||||
.color-no-match = कोई रंग मेल नहीं खाता
|
||||
.color-not-a-book = यह रंग पुस्तक नहीं है
|
||||
.color-book-encrypted = यह रंग पुस्तक एन्क्रिप्टेड है और पढ़ी नहीं जा सकती
|
||||
.color-book-empty = रंग पुस्तक में कोई रंग नहीं है
|
||||
.color-book-locations = रंग पुस्तकों के स्थान
|
||||
.cad-color-index-aci-10-249 = ACI रंग सूचकांक 10–249
|
||||
.color-family-shade-intensity = { common.color-family } → · { common.shade } / { properties.intensity } ↓
|
||||
.grayscale-250-255 = { common.grayscale } 250–255
|
||||
.recently-used = हाल में उपयोग किए गए
|
||||
.isometric-drafting = सममितीय आरेखन
|
||||
.f5-cycles-the-active-plane = F5 सक्रिय तल को क्रम से बदलता है।
|
||||
.units-5430a1 = { common.units }…
|
||||
|
||||
@@ -1422,15 +1422,31 @@ common =
|
||||
.dismiss = Bezárás
|
||||
.value-8a8de8 = ?
|
||||
.select-color-8dddf1 = { common.select-color }…
|
||||
.no-recent-colors-yet = Még nincsenek legutóbbi színek
|
||||
.true-color-rgb = { common.true-color } RGB __ocs_fmt_0__, __ocs_fmt_1__, __ocs_fmt_2__
|
||||
.index-color = { common.color } (ACI)
|
||||
.true-color = { common.color } (RGB)
|
||||
.standard-colors = { common.color } ({ common.standard })
|
||||
.color-hex = Hex
|
||||
.nearest-index-color = Legközelebbi index
|
||||
.exact-index-match = Pontos indexegyezés
|
||||
.color-recently-used = Legutóbb használt
|
||||
.color-most-used = Leggyakrabban használt
|
||||
.color-number = Színszám
|
||||
.color-name-az = Név A–Z
|
||||
.color-name-za = Név Z–A
|
||||
.color-sort = Rendezés
|
||||
.color-no-drawing-colors = Ebben a rajzban még nincsenek színek
|
||||
.color-books = Színkönyvek
|
||||
.color-no-books = Nincs betöltött színkönyv
|
||||
.color-load-book = Színkönyv betöltése (.acb)
|
||||
.color-book-label = Színkönyv
|
||||
.color-rgb-equivalent = RGB-megfelelő
|
||||
.color-no-match = Nincs egyező szín
|
||||
.color-not-a-book = Nem színkönyv
|
||||
.color-book-encrypted = Ez a színkönyv titkosított, és nem olvasható
|
||||
.color-book-empty = A színkönyv nem tartalmaz színeket
|
||||
.color-book-locations = Színkönyvek helye
|
||||
.cad-color-index-aci-10-249 = ACI színindex 10–249
|
||||
.color-family-shade-intensity = { common.color-family } → · { common.shade } / { properties.intensity } ↓
|
||||
.grayscale-250-255 = { common.grayscale } 250–255
|
||||
.recently-used = Legutóbb használt
|
||||
.isometric-drafting = Izometrikus rajzolás
|
||||
.f5-cycles-the-active-plane = Az F5 váltja az aktív síkot.
|
||||
.units-5430a1 = { common.units }…
|
||||
|
||||
@@ -1417,15 +1417,31 @@ common =
|
||||
.dismiss = Chiudi
|
||||
.value-8a8de8 = ?
|
||||
.select-color-8dddf1 = { common.select-color }…
|
||||
.no-recent-colors-yet = Nessun colore recente
|
||||
.true-color-rgb = { common.true-color } RGB __ocs_fmt_0__, __ocs_fmt_1__, __ocs_fmt_2__
|
||||
.index-color = { common.color } (ACI)
|
||||
.true-color = { common.color } (RGB)
|
||||
.standard-colors = { common.standard } · { common.color }
|
||||
.color-hex = Hex
|
||||
.nearest-index-color = Indice più vicino
|
||||
.exact-index-match = Corrispondenza esatta indice
|
||||
.color-recently-used = Usati di recente
|
||||
.color-most-used = Più usati
|
||||
.color-number = Numero colore
|
||||
.color-name-az = Nome A–Z
|
||||
.color-name-za = Nome Z–A
|
||||
.color-sort = Ordina
|
||||
.color-no-drawing-colors = Ancora nessun colore in questo disegno
|
||||
.color-books = Album colori
|
||||
.color-no-books = Nessun album colori caricato
|
||||
.color-load-book = Carica album colori (.acb)
|
||||
.color-book-label = Album colori
|
||||
.color-rgb-equivalent = Equivalente RGB
|
||||
.color-no-match = Nessun colore corrispondente
|
||||
.color-not-a-book = Non è un album colori
|
||||
.color-book-encrypted = Questo album colori è crittografato e non può essere letto
|
||||
.color-book-empty = L'album colori non contiene colori
|
||||
.color-book-locations = Percorsi degli album colori
|
||||
.cad-color-index-aci-10-249 = ACI Indice colori 10–249
|
||||
.color-family-shade-intensity = { common.color-family } → · { common.shade } / { properties.intensity } ↓
|
||||
.grayscale-250-255 = { common.grayscale } 250–255
|
||||
.recently-used = Usati di recente
|
||||
.isometric-drafting = Disegno isometrico
|
||||
.f5-cycles-the-active-plane = F5 alterna il piano attivo.
|
||||
.units-5430a1 = { common.units }…
|
||||
|
||||
@@ -1408,15 +1408,31 @@ common =
|
||||
.dismiss = 閉じる
|
||||
.value-8a8de8 = ?
|
||||
.select-color-8dddf1 = { common.select-color }…
|
||||
.no-recent-colors-yet = 最近使った色はありません
|
||||
.true-color-rgb = { common.true-color } RGB __ocs_fmt_0__, __ocs_fmt_1__, __ocs_fmt_2__
|
||||
.index-color = { common.color } (ACI)
|
||||
.true-color = { common.color } (RGB)
|
||||
.standard-colors = { common.standard } · { common.color }
|
||||
.color-hex = 16進
|
||||
.nearest-index-color = 最も近いインデックス
|
||||
.exact-index-match = インデックスと完全一致
|
||||
.color-recently-used = 最近使用した色
|
||||
.color-most-used = 使用頻度の高い色
|
||||
.color-number = 色番号
|
||||
.color-name-az = 名前 (昇順)
|
||||
.color-name-za = 名前 (降順)
|
||||
.color-sort = 並べ替え
|
||||
.color-no-drawing-colors = この図面にはまだ色がありません
|
||||
.color-books = カラー ブック
|
||||
.color-no-books = カラー ブックが読み込まれていません
|
||||
.color-load-book = カラー ブックを読み込む (.acb)
|
||||
.color-book-label = カラー ブック
|
||||
.color-rgb-equivalent = RGB 相当値
|
||||
.color-no-match = 一致する色がありません
|
||||
.color-not-a-book = カラー ブックではありません
|
||||
.color-book-encrypted = このカラー ブックは暗号化されているため読み込めません
|
||||
.color-book-empty = カラー ブックに色がありません
|
||||
.color-book-locations = カラー ブックの場所
|
||||
.cad-color-index-aci-10-249 = ACI カラーインデックス 10–249
|
||||
.color-family-shade-intensity = { common.color-family } → · { common.shade } / { properties.intensity } ↓
|
||||
.grayscale-250-255 = { common.grayscale } 250–255
|
||||
.recently-used = 最近使用した色
|
||||
.isometric-drafting = アイソメ図作成
|
||||
.f5-cycles-the-active-plane = F5でアクティブな平面を順に切り替えます。
|
||||
.units-5430a1 = { common.units }…
|
||||
|
||||
@@ -1417,15 +1417,31 @@ common =
|
||||
.dismiss = 닫기
|
||||
.value-8a8de8 = ?
|
||||
.select-color-8dddf1 = { common.select-color }…
|
||||
.no-recent-colors-yet = 최근 사용한 색상이 없습니다
|
||||
.true-color-rgb = { common.true-color } RGB __ocs_fmt_0__, __ocs_fmt_1__, __ocs_fmt_2__
|
||||
.index-color = { common.color } (ACI)
|
||||
.true-color = { common.color } (RGB)
|
||||
.standard-colors = { common.standard } · { common.color }
|
||||
.color-hex = 16진수
|
||||
.nearest-index-color = 가장 가까운 인덱스
|
||||
.exact-index-match = 인덱스와 정확히 일치
|
||||
.color-recently-used = 최근 사용
|
||||
.color-most-used = 가장 많이 사용
|
||||
.color-number = 색상 번호
|
||||
.color-name-az = 이름 오름차순
|
||||
.color-name-za = 이름 내림차순
|
||||
.color-sort = 정렬
|
||||
.color-no-drawing-colors = 이 도면에는 아직 색상이 없습니다
|
||||
.color-books = 색상표
|
||||
.color-no-books = 로드된 색상표가 없습니다
|
||||
.color-load-book = 색상표 로드 (.acb)
|
||||
.color-book-label = 색상표
|
||||
.color-rgb-equivalent = RGB 등가
|
||||
.color-no-match = 일치하는 색상이 없습니다
|
||||
.color-not-a-book = 색상표가 아닙니다
|
||||
.color-book-encrypted = 이 색상표는 암호화되어 있어 읽을 수 없습니다
|
||||
.color-book-empty = 색상표에 색상이 없습니다
|
||||
.color-book-locations = 색상표 위치
|
||||
.cad-color-index-aci-10-249 = ACI 컬러 인덱스 10–249
|
||||
.color-family-shade-intensity = { common.color-family } → · { common.shade } / { properties.intensity } ↓
|
||||
.grayscale-250-255 = { common.grayscale } 250–255
|
||||
.recently-used = 최근 사용
|
||||
.isometric-drafting = 등각 제도
|
||||
.f5-cycles-the-active-plane = F5 키로 활성 평면을 순환 전환합니다.
|
||||
.units-5430a1 = { common.units }…
|
||||
|
||||
@@ -1423,15 +1423,31 @@ common =
|
||||
.dismiss = ປິດອອກ
|
||||
.value-8a8de8 = ?
|
||||
.select-color-8dddf1 = ເລືອກສີ...
|
||||
.no-recent-colors-yet = ຍັງບໍ່ມີສີຫຼ້າສຸດ
|
||||
.true-color-rgb = True Color RGB __ocs_fmt_0__, __ocs_fmt_1__, __ocs_fmt_2__
|
||||
.index-color = ສີດັດຊະນີ
|
||||
.true-color = True Color
|
||||
.standard-colors = ສີມາດຕະຖານ
|
||||
.color-hex = Hex
|
||||
.nearest-index-color = ດັດຊະນີທີ່ໃກ້ທີ່ສຸດ
|
||||
.exact-index-match = ກົງກັບດັດຊະນີພໍດີ
|
||||
.color-recently-used = ໃຊ້ຫຼ້າສຸດ
|
||||
.color-most-used = ໃຊ້ຫຼາຍທີ່ສຸດ
|
||||
.color-number = ເລກສີ
|
||||
.color-name-az = ຊື່ A–Z
|
||||
.color-name-za = ຊື່ Z–A
|
||||
.color-sort = ຈັດລຽງ
|
||||
.color-no-drawing-colors = ຍັງບໍ່ມີສີໃນແບບແຕ້ມນີ້
|
||||
.color-books = ປຶ້ມສີ
|
||||
.color-no-books = ບໍ່ໄດ້ໂຫຼດປຶ້ມສີ
|
||||
.color-load-book = ໂຫຼດປຶ້ມສີ (.acb)
|
||||
.color-book-label = ປຶ້ມສີ
|
||||
.color-rgb-equivalent = ທຽບເທົ່າ RGB
|
||||
.color-no-match = ບໍ່ມີສີທີ່ກົງກັນ
|
||||
.color-not-a-book = ບໍ່ແມ່ນປຶ້ມສີ
|
||||
.color-book-encrypted = ປຶ້ມສີນີ້ຖືກເຂົ້າລະຫັດ ແລະອ່ານບໍ່ໄດ້
|
||||
.color-book-empty = ປຶ້ມສີບໍ່ມີສີ
|
||||
.color-book-locations = ທີ່ຕັ້ງປຶ້ມສີ
|
||||
.cad-color-index-aci-10-249 = CAD Color Index (ACI) 10–249
|
||||
.color-family-shade-intensity = ກຸ່ມສີ → · ລະດັບສີ / ຄວາມເຂັ້ມ ↓
|
||||
.grayscale-250-255 = ລະດັບສີເທົາ 250–255
|
||||
.recently-used = ໃຊ້ຫຼ້າສຸດ
|
||||
.isometric-drafting = ການຂຽນແບບໄອໂຊເມຕຣິກ
|
||||
.f5-cycles-the-active-plane = F5 ປ່ຽນລະນາບທີ່ໃຊ້ງານວຽນໄປ.
|
||||
.units-5430a1 = ຫົວໜ່ວຍ…
|
||||
|
||||
@@ -1416,15 +1416,31 @@ common =
|
||||
.dismiss = Sluiten
|
||||
.value-8a8de8 = ?
|
||||
.select-color-8dddf1 = { common.select-color }…
|
||||
.no-recent-colors-yet = Nog geen recente kleuren
|
||||
.true-color-rgb = { common.true-color } RGB __ocs_fmt_0__, __ocs_fmt_1__, __ocs_fmt_2__
|
||||
.index-color = { common.color } (ACI)
|
||||
.true-color = { common.color } (RGB)
|
||||
.standard-colors = { common.standard } · { common.color }
|
||||
.color-hex = Hex
|
||||
.nearest-index-color = Dichtstbijzijnde index
|
||||
.exact-index-match = Exacte indexkleur
|
||||
.color-recently-used = Recent gebruikt
|
||||
.color-most-used = Meest gebruikt
|
||||
.color-number = Kleurnummer
|
||||
.color-name-az = Naam A–Z
|
||||
.color-name-za = Naam Z–A
|
||||
.color-sort = Sorteren
|
||||
.color-no-drawing-colors = Nog geen kleuren in deze tekening
|
||||
.color-books = Kleurboeken
|
||||
.color-no-books = Geen kleurboeken geladen
|
||||
.color-load-book = Kleurboek laden (.acb)
|
||||
.color-book-label = Kleurboek
|
||||
.color-rgb-equivalent = RGB-equivalent
|
||||
.color-no-match = Geen kleuren gevonden
|
||||
.color-not-a-book = Geen kleurboek
|
||||
.color-book-encrypted = Dit kleurboek is versleuteld en kan niet worden gelezen
|
||||
.color-book-empty = Het kleurboek bevat geen kleuren
|
||||
.color-book-locations = Locaties van kleurboeken
|
||||
.cad-color-index-aci-10-249 = ACI Kleurindex 10–249
|
||||
.color-family-shade-intensity = { common.color-family } → · { common.shade } / { properties.intensity } ↓
|
||||
.grayscale-250-255 = { common.grayscale } 250–255
|
||||
.recently-used = Recent gebruikt
|
||||
.isometric-drafting = Isometrisch tekenen
|
||||
.f5-cycles-the-active-plane = F5 wisselt het actieve vlak.
|
||||
.units-5430a1 = { common.units }…
|
||||
|
||||
@@ -1417,15 +1417,31 @@ common =
|
||||
.dismiss = Zamknij
|
||||
.value-8a8de8 = ?
|
||||
.select-color-8dddf1 = { common.select-color }…
|
||||
.no-recent-colors-yet = Brak ostatnio używanych kolorów
|
||||
.true-color-rgb = { common.true-color } RGB __ocs_fmt_0__, __ocs_fmt_1__, __ocs_fmt_2__
|
||||
.index-color = { common.color } (ACI)
|
||||
.true-color = { common.color } (RGB)
|
||||
.standard-colors = { common.standard } · { common.color }
|
||||
.color-hex = Hex
|
||||
.nearest-index-color = Najbliższy indeks
|
||||
.exact-index-match = Dokładne dopasowanie indeksu
|
||||
.color-recently-used = Ostatnio używane
|
||||
.color-most-used = Najczęściej używane
|
||||
.color-number = Numer koloru
|
||||
.color-name-az = Nazwa A–Z
|
||||
.color-name-za = Nazwa Z–A
|
||||
.color-sort = Sortuj
|
||||
.color-no-drawing-colors = Brak kolorów w tym rysunku
|
||||
.color-books = Wzorniki kolorów
|
||||
.color-no-books = Nie wczytano wzorników kolorów
|
||||
.color-load-book = Wczytaj wzornik kolorów (.acb)
|
||||
.color-book-label = Wzornik kolorów
|
||||
.color-rgb-equivalent = Odpowiednik RGB
|
||||
.color-no-match = Brak pasujących kolorów
|
||||
.color-not-a-book = To nie jest wzornik kolorów
|
||||
.color-book-encrypted = Ten wzornik kolorów jest zaszyfrowany i nie można go odczytać
|
||||
.color-book-empty = Wzornik kolorów nie zawiera kolorów
|
||||
.color-book-locations = Lokalizacje wzorników kolorów
|
||||
.cad-color-index-aci-10-249 = Indeks kolorów ACI 10–249
|
||||
.color-family-shade-intensity = { common.color-family } → · { common.shade } / { properties.intensity } ↓
|
||||
.grayscale-250-255 = { common.grayscale } 250–255
|
||||
.recently-used = Ostatnio używane
|
||||
.isometric-drafting = Rysowanie izometryczne
|
||||
.f5-cycles-the-active-plane = F5 przełącza aktywną płaszczyznę.
|
||||
.units-5430a1 = { common.units }…
|
||||
|
||||
@@ -1418,15 +1418,31 @@ common =
|
||||
.dismiss = Fechar
|
||||
.value-8a8de8 = ?
|
||||
.select-color-8dddf1 = { common.select-color }…
|
||||
.no-recent-colors-yet = Nenhuma cor recente ainda
|
||||
.true-color-rgb = { common.true-color } RGB __ocs_fmt_0__, __ocs_fmt_1__, __ocs_fmt_2__
|
||||
.index-color = { common.color } (ACI)
|
||||
.true-color = { common.color } (RGB)
|
||||
.standard-colors = { common.standard } · { common.color }
|
||||
.color-hex = Hex
|
||||
.nearest-index-color = Índice mais próximo
|
||||
.exact-index-match = Corresponde exatamente ao índice
|
||||
.color-recently-used = Usadas recentemente
|
||||
.color-most-used = Mais usadas
|
||||
.color-number = Número da cor
|
||||
.color-name-az = Nome A–Z
|
||||
.color-name-za = Nome Z–A
|
||||
.color-sort = Ordenar
|
||||
.color-no-drawing-colors = Ainda não há cores neste desenho
|
||||
.color-books = Catálogos de cores
|
||||
.color-no-books = Nenhum catálogo de cores carregado
|
||||
.color-load-book = Carregar catálogo de cores (.acb)
|
||||
.color-book-label = Catálogo de cores
|
||||
.color-rgb-equivalent = Equivalente RGB
|
||||
.color-no-match = Nenhuma cor corresponde
|
||||
.color-not-a-book = Não é um catálogo de cores
|
||||
.color-book-encrypted = Este catálogo de cores é criptografado e não pode ser lido
|
||||
.color-book-empty = O catálogo de cores não tem cores
|
||||
.color-book-locations = Locais dos catálogos de cores
|
||||
.cad-color-index-aci-10-249 = Índice de cor ACI 10–249
|
||||
.color-family-shade-intensity = { common.color-family } → · { common.shade } / { properties.intensity } ↓
|
||||
.grayscale-250-255 = { common.grayscale } 250–255
|
||||
.recently-used = Usados recentemente
|
||||
.isometric-drafting = Desenho isométrico
|
||||
.f5-cycles-the-active-plane = F5 alterna o plano ativo.
|
||||
.units-5430a1 = { common.units }…
|
||||
|
||||
@@ -1421,15 +1421,31 @@ common =
|
||||
.dismiss = Закрыть
|
||||
.value-8a8de8 = ?
|
||||
.select-color-8dddf1 = { common.select-color }…
|
||||
.no-recent-colors-yet = Недавно использованных цветов пока нет
|
||||
.true-color-rgb = { common.true-color } RGB __ocs_fmt_0__, __ocs_fmt_1__, __ocs_fmt_2__
|
||||
.index-color = { common.color } (ACI)
|
||||
.true-color = { common.color } (RGB)
|
||||
.standard-colors = { common.standard } · { common.color }
|
||||
.color-hex = Hex
|
||||
.nearest-index-color = Ближайший индекс
|
||||
.exact-index-match = Точное совпадение с индексом
|
||||
.color-recently-used = Недавно использованные
|
||||
.color-most-used = Чаще всего используемые
|
||||
.color-number = Номер цвета
|
||||
.color-name-az = Имя А–Я
|
||||
.color-name-za = Имя Я–А
|
||||
.color-sort = Сортировка
|
||||
.color-no-drawing-colors = В этом чертеже пока нет цветов
|
||||
.color-books = Альбомы цветов
|
||||
.color-no-books = Альбомы цветов не загружены
|
||||
.color-load-book = Загрузить альбом цветов (.acb)
|
||||
.color-book-label = Альбом цветов
|
||||
.color-rgb-equivalent = Эквивалент RGB
|
||||
.color-no-match = Нет подходящих цветов
|
||||
.color-not-a-book = Это не альбом цветов
|
||||
.color-book-encrypted = Этот альбом цветов зашифрован и не может быть прочитан
|
||||
.color-book-empty = В альбоме цветов нет цветов
|
||||
.color-book-locations = Расположение альбомов цветов
|
||||
.cad-color-index-aci-10-249 = Индекс цвета ACI 10–249
|
||||
.color-family-shade-intensity = { common.color-family } → · { common.shade } / { properties.intensity } ↓
|
||||
.grayscale-250-255 = { common.grayscale } 250–255
|
||||
.recently-used = Недавно использованные
|
||||
.isometric-drafting = Изометрическое черчение
|
||||
.f5-cycles-the-active-plane = F5 циклически переключает активную плоскость.
|
||||
.units-5430a1 = { common.units }…
|
||||
|
||||
@@ -1404,15 +1404,31 @@ common =
|
||||
.dismiss = Kapat
|
||||
.value-8a8de8 = ?
|
||||
.select-color-8dddf1 = { common.select-color }…
|
||||
.no-recent-colors-yet = Henüz son kullanılan renk yok
|
||||
.true-color-rgb = { common.true-color } RGB __ocs_fmt_0__, __ocs_fmt_1__, __ocs_fmt_2__
|
||||
.index-color = { common.color } (ACI)
|
||||
.true-color = { common.color } (RGB)
|
||||
.standard-colors = { common.standard } · { common.color }
|
||||
.color-hex = Hex
|
||||
.nearest-index-color = En yakın indeks
|
||||
.exact-index-match = Tam indeks eşleşmesi
|
||||
.color-recently-used = Son kullanılanlar
|
||||
.color-most-used = En çok kullanılanlar
|
||||
.color-number = Renk numarası
|
||||
.color-name-az = Ad A–Z
|
||||
.color-name-za = Ad Z–A
|
||||
.color-sort = Sırala
|
||||
.color-no-drawing-colors = Bu çizimde henüz renk yok
|
||||
.color-books = Renk kitapları
|
||||
.color-no-books = Yüklenmiş renk kitabı yok
|
||||
.color-load-book = Renk kitabı yükle (.acb)
|
||||
.color-book-label = Renk kitabı
|
||||
.color-rgb-equivalent = RGB karşılığı
|
||||
.color-no-match = Eşleşen renk yok
|
||||
.color-not-a-book = Bir renk kitabı değil
|
||||
.color-book-encrypted = Bu renk kitabı şifreli ve okunamıyor
|
||||
.color-book-empty = Renk kitabında renk yok
|
||||
.color-book-locations = Renk kitabı konumları
|
||||
.cad-color-index-aci-10-249 = ACI renk dizini 10–249
|
||||
.color-family-shade-intensity = { common.color-family } → · { common.shade } / { properties.intensity } ↓
|
||||
.grayscale-250-255 = { common.grayscale } 250–255
|
||||
.recently-used = Son kullanılanlar
|
||||
.isometric-drafting = İzometrik çizim
|
||||
.f5-cycles-the-active-plane = F5 etkin düzlemi sırayla değiştirir.
|
||||
.units-5430a1 = { common.units }…
|
||||
|
||||
@@ -1405,15 +1405,31 @@ common =
|
||||
.dismiss = 关闭
|
||||
.value-8a8de8 = ?
|
||||
.select-color-8dddf1 = { common.select-color }…
|
||||
.no-recent-colors-yet = 尚无最近使用的颜色
|
||||
.true-color-rgb = { common.true-color } RGB __ocs_fmt_0__, __ocs_fmt_1__, __ocs_fmt_2__
|
||||
.index-color = { common.color } (ACI)
|
||||
.true-color = { common.color } (RGB)
|
||||
.standard-colors = { common.standard } · { common.color }
|
||||
.color-hex = 十六进制
|
||||
.nearest-index-color = 最接近的索引色
|
||||
.exact-index-match = 与索引色完全匹配
|
||||
.color-recently-used = 最近使用
|
||||
.color-most-used = 最常使用
|
||||
.color-number = 颜色编号
|
||||
.color-name-az = 名称 A–Z
|
||||
.color-name-za = 名称 Z–A
|
||||
.color-sort = 排序
|
||||
.color-no-drawing-colors = 此图形中还没有颜色
|
||||
.color-books = 配色系统
|
||||
.color-no-books = 未加载配色系统
|
||||
.color-load-book = 加载配色系统 (.acb)
|
||||
.color-book-label = 配色系统
|
||||
.color-rgb-equivalent = RGB 等效值
|
||||
.color-no-match = 没有匹配的颜色
|
||||
.color-not-a-book = 不是配色系统
|
||||
.color-book-encrypted = 此配色系统已加密,无法读取
|
||||
.color-book-empty = 配色系统中没有颜色
|
||||
.color-book-locations = 配色系统位置
|
||||
.cad-color-index-aci-10-249 = ACI 颜色索引 10–249
|
||||
.color-family-shade-intensity = { common.color-family } → · { common.shade } / { properties.intensity } ↓
|
||||
.grayscale-250-255 = { common.grayscale } 250–255
|
||||
.recently-used = 最近使用
|
||||
.isometric-drafting = 等轴测绘图
|
||||
.f5-cycles-the-active-plane = F5 循环切换活动平面。
|
||||
.units-5430a1 = { common.units }…
|
||||
|
||||
@@ -1417,15 +1417,31 @@ common =
|
||||
.dismiss = 關閉
|
||||
.value-8a8de8 = ?
|
||||
.select-color-8dddf1 = { common.select-color }…
|
||||
.no-recent-colors-yet = 尚無最近使用的顏色
|
||||
.true-color-rgb = { common.true-color } RGB __ocs_fmt_0__, __ocs_fmt_1__, __ocs_fmt_2__
|
||||
.index-color = { common.color } (ACI)
|
||||
.true-color = { common.color } (RGB)
|
||||
.standard-colors = { common.standard } · { common.color }
|
||||
.color-hex = 十六進位
|
||||
.nearest-index-color = 最接近的索引色
|
||||
.exact-index-match = 與索引色完全相符
|
||||
.color-recently-used = 最近使用
|
||||
.color-most-used = 最常使用
|
||||
.color-number = 顏色編號
|
||||
.color-name-az = 名稱 A–Z
|
||||
.color-name-za = 名稱 Z–A
|
||||
.color-sort = 排序
|
||||
.color-no-drawing-colors = 此圖面中還沒有顏色
|
||||
.color-books = 配色系統
|
||||
.color-no-books = 未載入配色系統
|
||||
.color-load-book = 載入配色系統 (.acb)
|
||||
.color-book-label = 配色系統
|
||||
.color-rgb-equivalent = RGB 等效值
|
||||
.color-no-match = 沒有相符的顏色
|
||||
.color-not-a-book = 不是配色系統
|
||||
.color-book-encrypted = 此配色系統已加密,無法讀取
|
||||
.color-book-empty = 配色系統中沒有顏色
|
||||
.color-book-locations = 配色系統位置
|
||||
.cad-color-index-aci-10-249 = ACI 顏色索引 10–249
|
||||
.color-family-shade-intensity = { common.color-family } → · { common.shade } / { properties.intensity } ↓
|
||||
.grayscale-250-255 = { common.grayscale } 250–255
|
||||
.recently-used = 最近使用
|
||||
.isometric-drafting = 等角製圖
|
||||
.f5-cycles-the-active-plane = F5 循環切換作用中平面。
|
||||
.units-5430a1 = { common.units }…
|
||||
|
||||
@@ -358,7 +358,11 @@ impl OpenCADStudio {
|
||||
if layer != "0" || entity.as_entity().layer().is_empty() {
|
||||
entity.as_entity_mut().set_layer(layer);
|
||||
}
|
||||
crate::scene::view::dispatch::apply_color(&mut entity, self.ribbon.active_color);
|
||||
crate::scene::view::dispatch::apply_named_color(
|
||||
&mut entity,
|
||||
self.ribbon.active_color,
|
||||
self.ribbon.active_color_name(),
|
||||
);
|
||||
crate::scene::view::dispatch::apply_common_prop(
|
||||
&mut entity,
|
||||
"linetype",
|
||||
@@ -476,7 +480,11 @@ impl OpenCADStudio {
|
||||
if layer != "0" || entity.as_entity().layer().is_empty() {
|
||||
entity.as_entity_mut().set_layer(layer);
|
||||
}
|
||||
crate::scene::view::dispatch::apply_color(&mut entity, self.ribbon.active_color);
|
||||
crate::scene::view::dispatch::apply_named_color(
|
||||
&mut entity,
|
||||
self.ribbon.active_color,
|
||||
self.ribbon.active_color_name(),
|
||||
);
|
||||
crate::scene::view::dispatch::apply_common_prop(
|
||||
&mut entity,
|
||||
"linetype",
|
||||
|
||||
@@ -391,8 +391,8 @@ impl OpenCADStudio {
|
||||
.location(id)
|
||||
.map_or(crate::app::config::DockSide::Right, |(side, _)| side),
|
||||
matches!(
|
||||
self.color_pick_target,
|
||||
Some((crate::app::ColorPickTarget::Gradient(_), _))
|
||||
self.color_picker.as_ref().map(|picker| &picker.target),
|
||||
Some(crate::app::ColorPickTarget::Gradient(_))
|
||||
),
|
||||
),
|
||||
crate::ui::dock::PanelId::BlockPalette => {
|
||||
|
||||
@@ -290,6 +290,8 @@ pub(super) struct DocumentTab {
|
||||
/// Model-space corners of the last crossing window drawn with no command
|
||||
/// running: STRETCH on that selection stretches through it (#1068).
|
||||
pub(super) last_crossing_window: Option<([f64; 2], [f64; 2])>,
|
||||
/// Colours picked in this drawing this session, newest first. Not saved.
|
||||
pub(super) recent_colors: Vec<codec::types::Color>,
|
||||
}
|
||||
|
||||
impl DocumentTab {
|
||||
@@ -697,6 +699,7 @@ impl DocumentTab {
|
||||
suspended_cmd: None,
|
||||
transparent_resume: false,
|
||||
last_crossing_window: None,
|
||||
recent_colors: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+29
-36
@@ -952,9 +952,6 @@ pub(super) struct OpenCADStudio {
|
||||
pub(crate) sheet_set_pending_layout: Option<(String, String)>,
|
||||
/// The subset / set Import Layout as Sheet adds to, while its file picker is open.
|
||||
pub(crate) sheet_set_import_parent: Option<String>,
|
||||
/// Which viewport background the colour wheel is editing, or `None` when
|
||||
/// it is closed. One slot, because only one wheel can be open at a time.
|
||||
pub(crate) bg_picker: Option<BgTarget>,
|
||||
/// General edge-stack dock layout for the side panels.
|
||||
pub(crate) dock: crate::ui::dock::DockState,
|
||||
/// Pallet revealed although it auto-hides (its edge's group, or its
|
||||
@@ -1074,13 +1071,14 @@ pub(super) struct OpenCADStudio {
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
web_autosave_clear_after: Vec<String>,
|
||||
// ── Floating panel windows ────────────────────────────────────────────
|
||||
/// Active `iced_aw` colour picker: destination plus its initial colour.
|
||||
color_pick_target: Option<(ColorPickTarget, AcadColor)>,
|
||||
/// Visible page in the shared CAD colour picker.
|
||||
color_picker_tab: ColorPickerTab,
|
||||
/// The open Select Color panel, if any.
|
||||
color_picker: Option<crate::ui::color_select::ColorPicker>,
|
||||
/// The built-in colour book and those in the colour book folder, read
|
||||
/// lazily when the Select Color panel first opens.
|
||||
color_books: std::sync::Arc<Vec<crate::ui::color_select::book::ColorBook>>,
|
||||
color_books_read: bool,
|
||||
/// Recently committed real colours, newest first.
|
||||
/// ACI and True Color remain semantically distinct even when their RGB matches.
|
||||
recent_colors: Vec<AcadColor>,
|
||||
/// The open in-canvas modal dialog, if any (Plan B: shared overlay instead
|
||||
/// of OS windows).
|
||||
active_modal: Option<ModalKind>,
|
||||
@@ -1788,21 +1786,19 @@ pub struct SaveOutcome {
|
||||
refreshed_preview: Option<Option<codec::Preview>>,
|
||||
result: Result<(), crate::io::SaveFailure>,
|
||||
}
|
||||
/// Which viewport background a colour-wheel session is editing.
|
||||
/// Which Options colour the interface colour picker is editing.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum BgTarget {
|
||||
Model,
|
||||
Paper,
|
||||
Desk,
|
||||
pub enum UiColor {
|
||||
ModelBackground,
|
||||
PaperBackground,
|
||||
DeskBackground,
|
||||
/// One of the six theme colours, by its row in Options.
|
||||
Theme(usize),
|
||||
Crosshair,
|
||||
SnapMarker,
|
||||
CommandText,
|
||||
}
|
||||
|
||||
/// Active page in the shared CAD colour picker.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
|
||||
pub enum ColorPickerTab {
|
||||
#[default]
|
||||
Index,
|
||||
TrueColor,
|
||||
}
|
||||
/// Where a colour chosen in the standalone palette window should be applied.
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum ColorPickTarget {
|
||||
@@ -1828,6 +1824,8 @@ pub enum ColorPickTarget {
|
||||
LayerState(usize),
|
||||
/// The MText editor's selection (or global) colour.
|
||||
MText,
|
||||
/// An Options interface colour (true colour only).
|
||||
Interface(UiColor),
|
||||
/// A colour of the view Background dialog: 0 solid, 1 top, 2 middle, 3 bottom.
|
||||
ViewBackground(u8),
|
||||
}
|
||||
@@ -2596,12 +2594,6 @@ pub enum Message {
|
||||
ModelSpaceModeChanged(config::ModelSpaceMode),
|
||||
/// Change Model Space custom background color as hex or empty for default.
|
||||
ModelSpaceBgChanged(String),
|
||||
/// Open the colour wheel on one of the viewport backgrounds.
|
||||
BgPickerOpen(BgTarget),
|
||||
/// Dismiss the wheel, changing nothing.
|
||||
BgPickerCancel,
|
||||
/// Accept the wheel's colour for whichever background it was opened on.
|
||||
BgPickerSubmit(iced::Color),
|
||||
/// Change Paper Space custom sheet background color as hex or empty for default.
|
||||
PaperSpaceBgChanged(String),
|
||||
/// Change Paper Space desk surround background (#RRGGBB).
|
||||
@@ -4344,14 +4336,16 @@ pub enum Message {
|
||||
DsColorMore(DsField),
|
||||
/// Open the shared CAD colour picker for a field and its current colour.
|
||||
OpenColorWindow(ColorPickTarget, AcadColor),
|
||||
/// Switch between Index Color and True Color.
|
||||
ColorPickerTabChanged(ColorPickerTab),
|
||||
/// Change the pending colour without committing it yet.
|
||||
ColorPickerColorChanged(AcadColor),
|
||||
/// Interaction inside the Select Color panel.
|
||||
ColorPicker(crate::ui::color_select::ColorPickerMsg),
|
||||
/// Close the colour picker without choosing.
|
||||
CloseColorPicker,
|
||||
/// Commit the colour chosen in the shared picker.
|
||||
ColorWindowPick(codec::types::Color),
|
||||
/// Commit the colour chosen in the shared picker, with its `BOOK$NAME`
|
||||
/// when it comes from a colour book.
|
||||
ColorWindowPick(codec::types::Color, Option<String>),
|
||||
/// An `.acb` file was read (and, on desktop, copied into the first
|
||||
/// colour book folder).
|
||||
ColorBookLoaded(Result<crate::ui::color_select::book::ColorBook, String>),
|
||||
/// Set a block/linetype Handle field on the selected dim style from a
|
||||
/// dropdown of available block-records / linetypes (by name).
|
||||
DsSetHandle {
|
||||
@@ -4756,7 +4750,6 @@ impl OpenCADStudio {
|
||||
sheet_set: Default::default(),
|
||||
sheet_set_pending_layout: None,
|
||||
sheet_set_import_parent: None,
|
||||
bg_picker: None,
|
||||
block_palette: Default::default(),
|
||||
xref_manager: Default::default(),
|
||||
dock: Default::default(),
|
||||
@@ -4805,9 +4798,9 @@ impl OpenCADStudio {
|
||||
web_autosave_consumed: None,
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
web_autosave_clear_after: Vec::new(),
|
||||
color_pick_target: None,
|
||||
color_picker_tab: ColorPickerTab::Index,
|
||||
recent_colors: Vec::new(),
|
||||
color_picker: None,
|
||||
color_books: Default::default(),
|
||||
color_books_read: false,
|
||||
active_modal: None,
|
||||
hyperlink_editor_handles: Vec::new(),
|
||||
hyperlink_editor_url: String::new(),
|
||||
|
||||
@@ -3160,7 +3160,11 @@ handles={handles_ms:.1} panel={:.1} ribbon={ribbon_ms:.1} tail={:.1} selected={}
|
||||
}
|
||||
|
||||
if !preserve_entity_style {
|
||||
crate::scene::view::dispatch::apply_color(&mut entity, self.ribbon.active_color);
|
||||
crate::scene::view::dispatch::apply_named_color(
|
||||
&mut entity,
|
||||
self.ribbon.active_color,
|
||||
self.ribbon.active_color_name(),
|
||||
);
|
||||
crate::scene::view::dispatch::apply_common_prop(
|
||||
&mut entity,
|
||||
"linetype",
|
||||
|
||||
+267
-102
@@ -436,6 +436,41 @@ impl OpenCADStudio {
|
||||
let tab = &self.tabs[self.active_tab];
|
||||
(started, tab.id, tab.scene.geometry_epoch)
|
||||
});
|
||||
// The Select Color panel can open on top of another dialog, so it
|
||||
// takes the keyboard first: Esc cancels, Enter confirms, the arrows
|
||||
// walk the index palette, and every other keystroke is swallowed.
|
||||
if self.color_picker.is_some() {
|
||||
use crate::ui::color_select::ColorPickerMsg;
|
||||
match &msg {
|
||||
Message::CommandEscape => {
|
||||
return self.update(Message::ColorPicker(ColorPickerMsg::Cancel));
|
||||
}
|
||||
Message::CommandFinalize => {
|
||||
return self.update(Message::ColorPicker(ColorPickerMsg::Ok));
|
||||
}
|
||||
Message::ShortcutPressed(key) => match key.rsplit('+').next() {
|
||||
Some("ESCAPE") => {
|
||||
return self.update(Message::ColorPicker(ColorPickerMsg::Cancel));
|
||||
}
|
||||
Some("ENTER" | "RETURN") => {
|
||||
return self.update(Message::ColorPicker(ColorPickerMsg::Ok));
|
||||
}
|
||||
Some("TAB") => {
|
||||
return crate::ui::color_select::picker::focus_step(
|
||||
!key.contains("SHIFT+"),
|
||||
);
|
||||
}
|
||||
_ => {}
|
||||
},
|
||||
Message::ArrowKeyPressed { direction, .. } => {
|
||||
return self.update(Message::ColorPicker(ColorPickerMsg::Arrow(*direction)));
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
if is_modal_blocked_key_msg(&msg) {
|
||||
return Task::none();
|
||||
}
|
||||
}
|
||||
// A modal dialog must capture the keyboard the same way it already
|
||||
// captures the mouse. Otherwise keystrokes from the global key
|
||||
// subscription leak past the modal into the command line and fire as
|
||||
@@ -4330,38 +4365,7 @@ impl OpenCADStudio {
|
||||
Task::none()
|
||||
}
|
||||
|
||||
Message::LayerColorSet(color) => {
|
||||
let i = self.active_tab;
|
||||
// Apply to every selected layer (multi-select), not just one.
|
||||
let names = self.selected_layer_names(i);
|
||||
if !names.is_empty() {
|
||||
let undo = self.begin_layer_undo(i, "LAYER COLOR", &names);
|
||||
for name in &names {
|
||||
if let Some(dl) = self.tabs[i].scene.document.layers.get_mut(name) {
|
||||
dl.color = color;
|
||||
dl.color_name = None;
|
||||
dl.book_name = None;
|
||||
}
|
||||
}
|
||||
for pl in self.tabs[i].layers.layers.iter_mut() {
|
||||
if names.contains(&pl.name) {
|
||||
pl.color = color;
|
||||
pl.color_name = None;
|
||||
pl.book_name = None;
|
||||
}
|
||||
}
|
||||
self.tabs[i].dirty = true;
|
||||
self.commit_layer_undo(i, undo);
|
||||
// ByLayer color is baked into the cached wires at
|
||||
// tessellation time, so bump the geometry epoch to
|
||||
// invalidate the wire cache and repaint with the new color.
|
||||
self.tabs[i].scene.invalidate_layer_dependencies(&names);
|
||||
self.tabs[i].layers.color_picker_row = None;
|
||||
self.tabs[i].layers.color_full_palette = false;
|
||||
self.sync_ribbon_layers();
|
||||
}
|
||||
Task::none()
|
||||
}
|
||||
Message::LayerColorSet(color) => self.set_layer_color(color, None),
|
||||
|
||||
Message::LayerLinetypeSet(lt) => {
|
||||
let i = self.active_tab;
|
||||
@@ -4473,7 +4477,7 @@ impl OpenCADStudio {
|
||||
Message::PaneClicked(pane) => self.on_pane_clicked(pane),
|
||||
Message::PaneDragged(ev) => self.on_pane_dragged(ev),
|
||||
Message::PaneMove(idx, local) => {
|
||||
if self.color_pick_target.is_some() {
|
||||
if self.color_picker.is_some() {
|
||||
return Task::none();
|
||||
}
|
||||
let p = self.pane_canvas_point(idx, local);
|
||||
@@ -7432,22 +7436,7 @@ impl OpenCADStudio {
|
||||
Task::none()
|
||||
}
|
||||
|
||||
Message::PropColorChanged(color) => {
|
||||
let i = self.active_tab;
|
||||
let handles = self.property_target_handles(i);
|
||||
if handles.is_empty() {
|
||||
let task = self.on_ribbon_color_changed(color);
|
||||
self.refresh_properties();
|
||||
return task;
|
||||
}
|
||||
self.apply_property_op(i, "CHPROP", &handles, |app, handle| {
|
||||
if let Some(entity) = app.tabs[i].scene.document.get_entity_mut(handle) {
|
||||
crate::scene::view::dispatch::apply_color(entity, color);
|
||||
}
|
||||
});
|
||||
self.tabs[i].properties.color_picker_open = false;
|
||||
Task::none()
|
||||
}
|
||||
Message::PropColorChanged(color) => self.set_property_color(color, None),
|
||||
|
||||
Message::PropLwChanged(lw) => {
|
||||
let i = self.active_tab;
|
||||
@@ -8958,39 +8947,6 @@ impl OpenCADStudio {
|
||||
Task::none()
|
||||
}
|
||||
|
||||
Message::BgPickerOpen(target) => {
|
||||
self.bg_picker = Some(target);
|
||||
Task::none()
|
||||
}
|
||||
|
||||
Message::BgPickerCancel => {
|
||||
self.bg_picker = None;
|
||||
Task::none()
|
||||
}
|
||||
|
||||
Message::BgPickerSubmit(color) => {
|
||||
// Hand the result to the same handler the typed hex field
|
||||
// uses, so the wheel and the field cannot drift apart on
|
||||
// validation, persistence or the MatchTheme fallback.
|
||||
let hex = crate::app::config::rgb_to_hex([
|
||||
(color.r * 255.0).round() as u8,
|
||||
(color.g * 255.0).round() as u8,
|
||||
(color.b * 255.0).round() as u8,
|
||||
]);
|
||||
match self.bg_picker.take() {
|
||||
Some(crate::app::BgTarget::Model) => {
|
||||
Task::done(Message::ModelSpaceBgChanged(hex))
|
||||
}
|
||||
Some(crate::app::BgTarget::Paper) => {
|
||||
Task::done(Message::PaperSpaceBgChanged(hex))
|
||||
}
|
||||
Some(crate::app::BgTarget::Desk) => {
|
||||
Task::done(Message::DeskSpaceBgChanged(hex))
|
||||
}
|
||||
None => Task::none(),
|
||||
}
|
||||
}
|
||||
|
||||
Message::ModelSpaceBgChanged(hex) => {
|
||||
self.model_bg_input = hex.clone();
|
||||
if hex.trim().is_empty() {
|
||||
@@ -11922,10 +11878,21 @@ impl OpenCADStudio {
|
||||
) {
|
||||
self.graphic_attributes.open_menu = None;
|
||||
}
|
||||
self.color_pick_target = Some((target, color));
|
||||
|
||||
// Always open the shared CAD colour picker on the indexed ACI page.
|
||||
self.color_picker_tab = crate::app::ColorPickerTab::Index;
|
||||
let (current, varies, name) = self.color_picker_current(&target, color);
|
||||
let mut picker = crate::ui::color_select::ColorPicker::new(target, current, varies);
|
||||
if !matches!(picker.target, crate::app::ColorPickTarget::Interface(_)) {
|
||||
self.read_color_books();
|
||||
let by_layer = self.color_picker_layer_color(&picker.target);
|
||||
let tab = &self.tabs[self.active_tab];
|
||||
picker = picker.with_drawing(
|
||||
crate::ui::color_select::document::collect(&tab.scene.document),
|
||||
&tab.recent_colors,
|
||||
std::sync::Arc::clone(&self.color_books),
|
||||
name,
|
||||
by_layer,
|
||||
);
|
||||
}
|
||||
self.color_picker = Some(picker);
|
||||
self.modal_offset = iced::Vector::ZERO;
|
||||
self.ds_color_open = None;
|
||||
self.mls_color_open = None;
|
||||
@@ -11940,29 +11907,226 @@ impl OpenCADStudio {
|
||||
Task::none()
|
||||
}
|
||||
|
||||
Message::ColorPickerTabChanged(tab) => {
|
||||
self.color_picker_tab = tab;
|
||||
Task::none()
|
||||
}
|
||||
Message::ColorPickerColorChanged(color) => {
|
||||
if let Some((_, current)) = self.color_pick_target.as_mut() {
|
||||
*current = color;
|
||||
Message::ColorPicker(msg) => {
|
||||
let Some(picker) = self.color_picker.as_mut() else {
|
||||
return Task::none();
|
||||
};
|
||||
match picker.update(msg) {
|
||||
Some(crate::ui::color_select::Outcome::Commit(color, name)) => {
|
||||
self.update(Message::ColorWindowPick(color, name))
|
||||
}
|
||||
Some(crate::ui::color_select::Outcome::LoadBook) => Task::perform(
|
||||
crate::ui::color_select::book::load_from_dialog(),
|
||||
Message::ColorBookLoaded,
|
||||
),
|
||||
Some(crate::ui::color_select::Outcome::Cancel) => {
|
||||
self.color_picker = None;
|
||||
Task::none()
|
||||
}
|
||||
Some(crate::ui::color_select::Outcome::Screenshot) => {
|
||||
use crate::ui::color_select::{eyedropper::EyedropperMsg, ColorPickerMsg};
|
||||
match self.main_window {
|
||||
Some(id) => iced::window::screenshot(id).map(|shot| {
|
||||
Message::ColorPicker(ColorPickerMsg::Eyedropper(
|
||||
EyedropperMsg::Shot(shot),
|
||||
))
|
||||
}),
|
||||
// Nothing to capture: leave the eyedropper instead
|
||||
// of waiting for a screenshot that never comes.
|
||||
None => self.update(Message::ColorPicker(ColorPickerMsg::Cancel)),
|
||||
}
|
||||
}
|
||||
None => Task::none(),
|
||||
}
|
||||
Task::none()
|
||||
}
|
||||
Message::CloseColorPicker => {
|
||||
self.color_pick_target = None;
|
||||
self.color_picker = None;
|
||||
Task::none()
|
||||
}
|
||||
|
||||
Message::ColorWindowPick(color) => {
|
||||
self.note_recent_color(color);
|
||||
self.on_color_window_pick(color)
|
||||
Message::ColorWindowPick(color, name) => {
|
||||
// Interface colours are not drawing colours: keep them out of
|
||||
// the recent list.
|
||||
if !matches!(
|
||||
self.color_picker.as_ref().map(|picker| &picker.target),
|
||||
Some(crate::app::ColorPickTarget::Interface(_))
|
||||
) {
|
||||
self.note_recent_color(color);
|
||||
}
|
||||
self.on_color_window_pick(color, name)
|
||||
}
|
||||
Message::ColorBookLoaded(Ok(book)) => {
|
||||
// A book loaded again replaces the earlier copy.
|
||||
let index = crate::ui::color_select::book::add_or_replace(
|
||||
std::sync::Arc::make_mut(&mut self.color_books),
|
||||
book,
|
||||
);
|
||||
if let Some(picker) = self.color_picker.as_mut() {
|
||||
picker.show_book(std::sync::Arc::clone(&self.color_books), index);
|
||||
}
|
||||
Task::none()
|
||||
}
|
||||
Message::ColorBookLoaded(Err(error)) => {
|
||||
if let Some(picker) = self.color_picker.as_mut().filter(|_| error != "Cancelled") {
|
||||
picker.set_status(error);
|
||||
}
|
||||
Task::none()
|
||||
}
|
||||
Message::DsSetHandle { field, value } => self.on_ds_set_handle(field, value),
|
||||
}
|
||||
}
|
||||
|
||||
/// The colour the Select Color panel shows as "Current", and whether the
|
||||
/// selection it edits has mixed colours. A mixed selection starts from the
|
||||
/// colour of its most recently drawn object (the highest handle).
|
||||
/// Also returns the colour-book identity (`BOOK$NAME`) of that colour.
|
||||
fn color_picker_current(
|
||||
&self,
|
||||
target: &crate::app::ColorPickTarget,
|
||||
color: codec::types::Color,
|
||||
) -> (codec::types::Color, bool, Option<String>) {
|
||||
let i = self.active_tab;
|
||||
let document = &self.tabs[i].scene.document;
|
||||
let name_of = |handle| {
|
||||
document
|
||||
.get_entity(handle)
|
||||
.and_then(|entity| entity.common().color_name.clone())
|
||||
};
|
||||
match target {
|
||||
crate::app::ColorPickTarget::Properties => {}
|
||||
crate::app::ColorPickTarget::Ribbon => {
|
||||
return (color, false, self.ribbon.active_color_name());
|
||||
}
|
||||
crate::app::ColorPickTarget::Layer(layer) => {
|
||||
let name = document
|
||||
.layers
|
||||
.get(layer)
|
||||
.and_then(crate::ui::color_select::document::layer_identity);
|
||||
return (color, false, name);
|
||||
}
|
||||
_ => return (color, false, None),
|
||||
}
|
||||
let mut colors = self
|
||||
.property_target_handles(i)
|
||||
.into_iter()
|
||||
.filter_map(|handle| {
|
||||
document
|
||||
.get_entity(handle)
|
||||
.map(|entity| (handle, entity.common().color))
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
let Some(&(first_handle, first)) = colors.first() else {
|
||||
return (color, false, None);
|
||||
};
|
||||
if colors.iter().all(|&(_, c)| c == first) {
|
||||
return (first, false, name_of(first_handle));
|
||||
}
|
||||
colors.sort_by_key(|&(handle, _)| handle);
|
||||
let (newest_handle, newest) = colors.last().copied().unwrap_or((first_handle, color));
|
||||
(newest, true, name_of(newest_handle))
|
||||
}
|
||||
|
||||
/// The colour ByLayer stands for in the Select Color panel: the layer of
|
||||
/// the newest selected object, or the current layer for new objects.
|
||||
fn color_picker_layer_color(
|
||||
&self,
|
||||
target: &crate::app::ColorPickTarget,
|
||||
) -> Option<codec::types::Color> {
|
||||
let i = self.active_tab;
|
||||
let document = &self.tabs[i].scene.document;
|
||||
let layer = match target {
|
||||
crate::app::ColorPickTarget::Properties => self
|
||||
.property_target_handles(i)
|
||||
.into_iter()
|
||||
.max()
|
||||
.and_then(|handle| document.get_entity(handle))
|
||||
.map_or(self.tabs[i].active_layer.as_str(), |entity| {
|
||||
entity.as_entity().layer()
|
||||
}),
|
||||
crate::app::ColorPickTarget::Ribbon => self.tabs[i].active_layer.as_str(),
|
||||
_ => return None,
|
||||
};
|
||||
document.layers.get(layer).map(|layer| layer.color)
|
||||
}
|
||||
|
||||
/// Read the built-in colour book and every `.acb` in the colour book
|
||||
/// folder, once per session. A book in the folder replaces a built-in
|
||||
/// one of the same name.
|
||||
fn read_color_books(&mut self) {
|
||||
if std::mem::replace(&mut self.color_books_read, true) {
|
||||
return;
|
||||
}
|
||||
self.color_books = std::sync::Arc::new(crate::ui::color_select::book::read_all());
|
||||
}
|
||||
|
||||
/// Colour the selected objects (or set the current colour when nothing
|
||||
/// is selected). `name` is a colour-book identity, `BOOK$NAME`.
|
||||
pub(super) fn set_property_color(
|
||||
&mut self,
|
||||
color: codec::types::Color,
|
||||
name: Option<String>,
|
||||
) -> Task<Message> {
|
||||
let i = self.active_tab;
|
||||
let handles = self.property_target_handles(i);
|
||||
if handles.is_empty() {
|
||||
let task = self.on_ribbon_color_changed(color);
|
||||
self.ribbon.active_color_book = name.map(|name| (color, name));
|
||||
self.refresh_properties();
|
||||
return task;
|
||||
}
|
||||
self.apply_property_op(i, "CHPROP", &handles, |app, handle| {
|
||||
if let Some(entity) = app.tabs[i].scene.document.get_entity_mut(handle) {
|
||||
crate::scene::view::dispatch::apply_named_color(entity, color, name.clone());
|
||||
}
|
||||
});
|
||||
self.tabs[i].properties.color_picker_open = false;
|
||||
Task::none()
|
||||
}
|
||||
|
||||
/// Colour every selected layer (multi-select), not just one. `name` is a
|
||||
/// colour-book identity, `BOOK$NAME`.
|
||||
pub(super) fn set_layer_color(
|
||||
&mut self,
|
||||
color: codec::types::Color,
|
||||
name: Option<&str>,
|
||||
) -> Task<Message> {
|
||||
let i = self.active_tab;
|
||||
// Layers keep the book and the colour name apart.
|
||||
let (book_name, color_name) = match name.map(|name| name.split_once('$')) {
|
||||
Some(Some((book, name))) => (Some(book), Some(name)),
|
||||
Some(None) => (None, name),
|
||||
None => (None, None),
|
||||
};
|
||||
let names = self.selected_layer_names(i);
|
||||
if !names.is_empty() {
|
||||
let undo = self.begin_layer_undo(i, "LAYER COLOR", &names);
|
||||
for name in &names {
|
||||
if let Some(dl) = self.tabs[i].scene.document.layers.get_mut(name) {
|
||||
dl.color = color;
|
||||
dl.color_name = color_name.map(str::to_string);
|
||||
dl.book_name = book_name.map(str::to_string);
|
||||
}
|
||||
}
|
||||
for pl in self.tabs[i].layers.layers.iter_mut() {
|
||||
if names.contains(&pl.name) {
|
||||
pl.color = color;
|
||||
pl.color_name = color_name.map(str::to_string);
|
||||
pl.book_name = book_name.map(str::to_string);
|
||||
}
|
||||
}
|
||||
self.tabs[i].dirty = true;
|
||||
self.commit_layer_undo(i, undo);
|
||||
// ByLayer color is baked into the cached wires at
|
||||
// tessellation time, so bump the geometry epoch to
|
||||
// invalidate the wire cache and repaint with the new color.
|
||||
self.tabs[i].scene.invalidate_layer_dependencies(&names);
|
||||
self.tabs[i].layers.color_picker_row = None;
|
||||
self.tabs[i].layers.color_full_palette = false;
|
||||
self.sync_ribbon_layers();
|
||||
}
|
||||
Task::none()
|
||||
}
|
||||
|
||||
pub(crate) fn note_recent_color(&mut self, color: codec::types::Color) {
|
||||
// Keep only real colours in the recent list. ByLayer / ByBlock / None
|
||||
// are logical CAD states rather than reusable colours.
|
||||
@@ -11971,12 +12135,13 @@ impl OpenCADStudio {
|
||||
codec::types::Color::Index(_) | codec::types::Color::Rgb { .. }
|
||||
) {
|
||||
// No duplicates: selecting an existing colour moves it to the front.
|
||||
if let Some(pos) = self.recent_colors.iter().position(|c| c == &color) {
|
||||
self.recent_colors.remove(pos);
|
||||
let recent = &mut self.tabs[self.active_tab].recent_colors;
|
||||
if let Some(pos) = recent.iter().position(|c| c == &color) {
|
||||
recent.remove(pos);
|
||||
}
|
||||
|
||||
self.recent_colors.insert(0, color);
|
||||
self.recent_colors.truncate(12);
|
||||
recent.insert(0, color);
|
||||
recent.truncate(12);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+54
-27
@@ -1136,42 +1136,60 @@ impl OpenCADStudio {
|
||||
Task::none()
|
||||
}
|
||||
|
||||
pub(super) fn on_color_window_pick(&mut self, color: codec::types::Color) -> Task<Message> {
|
||||
pub(super) fn on_color_window_pick(
|
||||
&mut self,
|
||||
color: codec::types::Color,
|
||||
name: Option<String>,
|
||||
) -> Task<Message> {
|
||||
if let Some(crate::app::ColorPickTarget::Interface(target)) = self
|
||||
.color_picker
|
||||
.as_ref()
|
||||
.map(|picker| picker.target.clone())
|
||||
{
|
||||
self.color_picker = None;
|
||||
let Some((r, g, b)) = crate::ui::color_select::math::color_rgb(color) else {
|
||||
return Task::none();
|
||||
};
|
||||
// Hand the result to the same handler the typed hex field uses, so
|
||||
// the picker and the field cannot drift apart on validation or
|
||||
// persistence.
|
||||
let hex = crate::app::config::rgb_to_hex([r, g, b]);
|
||||
use crate::app::UiColor;
|
||||
return Task::done(match target {
|
||||
UiColor::ModelBackground => Message::ModelSpaceBgChanged(hex),
|
||||
UiColor::PaperBackground => Message::PaperSpaceBgChanged(hex),
|
||||
UiColor::DeskBackground => Message::DeskSpaceBgChanged(hex),
|
||||
UiColor::Theme(index) => Message::OptionsThemeColorChanged(index, hex),
|
||||
UiColor::Crosshair => Message::CrosshairColorChanged(hex),
|
||||
UiColor::SnapMarker => Message::SnapMarkerColorChanged(hex),
|
||||
UiColor::CommandText => Message::CommandTextColorChanged(hex),
|
||||
});
|
||||
}
|
||||
if matches!(
|
||||
self.color_pick_target.as_ref().map(|(target, _)| target),
|
||||
self.color_picker.as_ref().map(|picker| &picker.target),
|
||||
Some(crate::app::ColorPickTarget::PlotStyle)
|
||||
) {
|
||||
self.color_pick_target = None;
|
||||
self.color_picker = None;
|
||||
|
||||
let rgb = match color {
|
||||
codec::types::Color::Rgb { r, g, b } => Some((r, g, b)),
|
||||
codec::types::Color::Index(index) => codec::types::aci_table::aci_to_rgb(index),
|
||||
_ => None,
|
||||
};
|
||||
|
||||
if let Some((r, g, b)) = rgb {
|
||||
self.ps_color_buf = format!("#{r:02X}{g:02X}{b:02X}");
|
||||
if let Some((r, g, b)) = crate::ui::color_select::math::color_rgb(color) {
|
||||
self.ps_color_buf = crate::ui::color_select::math::hex_string(r, g, b);
|
||||
}
|
||||
|
||||
return self.on_plot_style_panel_apply();
|
||||
}
|
||||
let s = crate::ui::color_select::color_to_aci_string(color);
|
||||
let edit = match self.color_pick_target.take().map(|(target, _)| target) {
|
||||
let edit = match self.color_picker.take().map(|picker| picker.target) {
|
||||
Some(crate::app::ColorPickTarget::Gradient(index)) => {
|
||||
Some(Message::GraphicAttributes(GraphicAttributesMsg::Gradient(
|
||||
crate::ui::window::gradient_editor::GradientMsg::Color(index, color),
|
||||
)))
|
||||
}
|
||||
Some(crate::app::ColorPickTarget::ViewBackground(slot)) => {
|
||||
let rgb = match color {
|
||||
codec::types::Color::Rgb { r, g, b } => Some([r, g, b]),
|
||||
codec::types::Color::Index(index) => {
|
||||
codec::types::aci_table::aci_to_rgb(index).map(|(r, g, b)| [r, g, b])
|
||||
}
|
||||
_ => None,
|
||||
};
|
||||
rgb.map(|rgb| {
|
||||
Message::PdfDialog(crate::ui::window::pdf_dialogs::PdfDialogMsg::BgColor(slot, rgb))
|
||||
crate::ui::color_select::math::color_rgb(color).map(|(r, g, b)| {
|
||||
Message::PdfDialog(crate::ui::window::pdf_dialogs::PdfDialogMsg::BgColor(
|
||||
slot,
|
||||
[r, g, b],
|
||||
))
|
||||
})
|
||||
}
|
||||
Some(crate::app::ColorPickTarget::GraphicAttributesLine) => Some(
|
||||
@@ -1189,7 +1207,9 @@ impl OpenCADStudio {
|
||||
field: f,
|
||||
value: s,
|
||||
}),
|
||||
Some(crate::app::ColorPickTarget::Properties) => Some(Message::PropColorChanged(color)),
|
||||
Some(crate::app::ColorPickTarget::Properties) => {
|
||||
return self.set_property_color(color, name);
|
||||
}
|
||||
Some(crate::app::ColorPickTarget::PropertiesBg) => {
|
||||
Some(Message::PropBgColorChanged(color))
|
||||
}
|
||||
@@ -1197,13 +1217,17 @@ impl OpenCADStudio {
|
||||
Some(Message::PropColorFieldChanged { field, color })
|
||||
}
|
||||
Some(crate::app::ColorPickTarget::MText) => Some(Message::MTextColorChanged(color)),
|
||||
Some(crate::app::ColorPickTarget::Ribbon) => Some(Message::RibbonColorChanged(color)),
|
||||
Some(crate::app::ColorPickTarget::Layer(name)) => {
|
||||
Some(crate::app::ColorPickTarget::Ribbon) => {
|
||||
let task = self.update(Message::RibbonColorChanged(color));
|
||||
self.ribbon.active_color_book = name.map(|name| (color, name));
|
||||
return task;
|
||||
}
|
||||
Some(crate::app::ColorPickTarget::Layer(layer)) => {
|
||||
let i = self.active_tab;
|
||||
match self.layer_panel_index(i, &name) {
|
||||
match self.layer_panel_index(i, &layer) {
|
||||
Some(idx) => {
|
||||
self.tabs[i].layers.selected = Some(idx);
|
||||
Some(Message::LayerColorSet(color))
|
||||
return self.set_layer_color(color, name.as_deref());
|
||||
}
|
||||
None => None,
|
||||
}
|
||||
@@ -1211,7 +1235,10 @@ impl OpenCADStudio {
|
||||
Some(crate::app::ColorPickTarget::LayerState(idx)) => {
|
||||
Some(Message::LayerStateEditorLayerColor(idx, color))
|
||||
}
|
||||
Some(crate::app::ColorPickTarget::PlotStyle) => None,
|
||||
// Handled before this match.
|
||||
Some(
|
||||
crate::app::ColorPickTarget::PlotStyle | crate::app::ColorPickTarget::Interface(_),
|
||||
) => None,
|
||||
None => None,
|
||||
};
|
||||
if let Some(m) = edit {
|
||||
|
||||
@@ -1144,7 +1144,7 @@ impl OpenCADStudio {
|
||||
p: Point,
|
||||
expected_viewport: Option<codec::Handle>,
|
||||
) -> Task<Message> {
|
||||
if self.color_pick_target.is_some() {
|
||||
if self.color_picker.is_some() {
|
||||
return Task::none();
|
||||
}
|
||||
|
||||
@@ -1232,7 +1232,7 @@ impl OpenCADStudio {
|
||||
// leaks through the stack to the pane mouse_area beneath and
|
||||
// would track the crosshair over the dropdown's empty areas.
|
||||
// Drop the move here instead. (#227)
|
||||
if self.ribbon.open_dropdown.is_some() || self.color_pick_target.is_some() {
|
||||
if self.ribbon.open_dropdown.is_some() || self.color_picker.is_some() {
|
||||
return Task::none();
|
||||
}
|
||||
let i = self.active_tab;
|
||||
|
||||
+14
-36
@@ -2303,7 +2303,7 @@ bg={bg_ms:.1}ms n={view_count}"
|
||||
&redo_labels,
|
||||
self.win_size,
|
||||
self.tabs[self.active_tab].is_start,
|
||||
&self.recent_colors,
|
||||
&self.tabs[self.active_tab].recent_colors,
|
||||
)
|
||||
.unwrap_or_else(|| iced::widget::Space::new().width(0).height(0).into())
|
||||
};
|
||||
@@ -2431,42 +2431,20 @@ bg={bg_ms:.1}ms n={view_count}"
|
||||
}
|
||||
}
|
||||
|
||||
if let Some((_, current)) = self.color_pick_target.as_ref() {
|
||||
match self.color_picker_tab {
|
||||
super::ColorPickerTab::Index => {
|
||||
let content =
|
||||
crate::ui::color_select::index_color_page(*current, &self.recent_colors);
|
||||
|
||||
crate::ui::modal::modal(
|
||||
base,
|
||||
t!("Select Color"),
|
||||
content,
|
||||
Message::CloseColorPicker,
|
||||
self.modal_offset,
|
||||
crate::ui::modal::ModalOptions::NOTICE,
|
||||
)
|
||||
}
|
||||
|
||||
super::ColorPickerTab::TrueColor => {
|
||||
let initial = crate::ui::properties::acad_color_display(*current).0;
|
||||
let modal_base = crate::ui::modal::backdrop(base, Message::CloseColorPicker);
|
||||
|
||||
iced_aw::ColorPicker::new(
|
||||
true,
|
||||
initial,
|
||||
modal_base,
|
||||
// Cancel inside True Color returns to the indexed page instead
|
||||
// of closing the whole CAD colour picker.
|
||||
Message::ColorPickerTabChanged(super::ColorPickerTab::Index),
|
||||
|color| {
|
||||
Message::ColorWindowPick(crate::ui::color_select::iced_to_acad_color(
|
||||
color,
|
||||
))
|
||||
},
|
||||
)
|
||||
.into()
|
||||
}
|
||||
if let Some(picker) = self.color_picker.as_ref() {
|
||||
// While the eyedropper is picking, the panel steps aside so the
|
||||
// whole window can be sampled.
|
||||
if let Some(eyedropper) = picker.eyedropper_view() {
|
||||
return iced::widget::stack![base, eyedropper].into();
|
||||
}
|
||||
crate::ui::modal::modal(
|
||||
base,
|
||||
t!("Select Color"),
|
||||
picker.view(),
|
||||
Message::CloseColorPicker,
|
||||
self.modal_offset,
|
||||
crate::ui::modal::ModalOptions::NOTICE,
|
||||
)
|
||||
} else {
|
||||
base
|
||||
}
|
||||
|
||||
@@ -373,6 +373,8 @@ impl OpenCADStudio {
|
||||
.map(|_| std::env::temp_dir().display().to_string()),
|
||||
fonts: crate::io::font_repo::fonts_dir()
|
||||
.map(|p| p.display().to_string()),
|
||||
color_books: crate::ui::color_select::book::folder()
|
||||
.map(|p| p.display().to_string()),
|
||||
}
|
||||
}
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
@@ -395,7 +397,6 @@ impl OpenCADStudio {
|
||||
&self.model_bg_input,
|
||||
&self.paper_bg_input,
|
||||
&self.desk_bg_input,
|
||||
self.bg_picker,
|
||||
dirty,
|
||||
close_confirm,
|
||||
flow,
|
||||
|
||||
+20
-4
@@ -4931,15 +4931,31 @@ pub(super) fn message_attribute(source: &str) -> Option<(&'static str, &'static
|
||||
"Dismiss" => Some(("common", "dismiss")),
|
||||
"?" => Some(("common", "value-8a8de8")),
|
||||
"Select Color..." => Some(("common", "select-color-8dddf1")),
|
||||
"No recent colors yet" => Some(("common", "no-recent-colors-yet")),
|
||||
"True Color RGB {r}, {g}, {b}" => Some(("common", "true-color-rgb")),
|
||||
"Index Color" => Some(("common", "index-color")),
|
||||
"True Color" => Some(("common", "true-color")),
|
||||
"Standard colors" => Some(("common", "standard-colors")),
|
||||
"Hex" => Some(("common", "color-hex")),
|
||||
"Nearest index" => Some(("common", "nearest-index-color")),
|
||||
"Exact index match" => Some(("common", "exact-index-match")),
|
||||
"Recently used" => Some(("common", "color-recently-used")),
|
||||
"Most used" => Some(("common", "color-most-used")),
|
||||
"Color number" => Some(("common", "color-number")),
|
||||
"Name A–Z" => Some(("common", "color-name-az")),
|
||||
"Name Z–A" => Some(("common", "color-name-za")),
|
||||
"Sort" => Some(("common", "color-sort")),
|
||||
"No colors in this drawing yet" => Some(("common", "color-no-drawing-colors")),
|
||||
"Color Books" => Some(("common", "color-books")),
|
||||
"No color books loaded" => Some(("common", "color-no-books")),
|
||||
"Load color book (.acb)" => Some(("common", "color-load-book")),
|
||||
"Color book" => Some(("common", "color-book-label")),
|
||||
"RGB equivalent" => Some(("common", "color-rgb-equivalent")),
|
||||
"No colors match" => Some(("common", "color-no-match")),
|
||||
"Not a color book" => Some(("common", "color-not-a-book")),
|
||||
"This color book is encrypted and cannot be read" => Some(("common", "color-book-encrypted")),
|
||||
"The color book has no colors" => Some(("common", "color-book-empty")),
|
||||
"Color book locations" => Some(("common", "color-book-locations")),
|
||||
"CAD Color Index (ACI) 10–249" => Some(("common", "cad-color-index-aci-10-249")),
|
||||
"Color family → · Shade / intensity ↓" => Some(("common", "color-family-shade-intensity")),
|
||||
"Grayscale 250–255" => Some(("common", "grayscale-250-255")),
|
||||
"Recently used" => Some(("common", "recently-used")),
|
||||
"Isometric drafting" => Some(("common", "isometric-drafting")),
|
||||
"F5 cycles the active plane." => Some(("common", "f5-cycles-the-active-plane")),
|
||||
"Units…" => Some(("common", "units-5430a1")),
|
||||
|
||||
@@ -322,6 +322,12 @@ pub fn apply_color(entity: &mut EntityType, color: AcadColor) {
|
||||
common.color_book_handle = None;
|
||||
}
|
||||
|
||||
/// Like [`apply_color`], but keeps a colour-book identity (`BOOK$NAME`).
|
||||
pub fn apply_named_color(entity: &mut EntityType, color: AcadColor, name: Option<String>) {
|
||||
apply_color(entity, color);
|
||||
entity.common_mut().color_name = name;
|
||||
}
|
||||
|
||||
pub fn apply_line_weight(entity: &mut EntityType, lw: LineWeight) {
|
||||
entity.as_entity_mut().set_line_weight(lw);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,251 @@
|
||||
//! Colour books: AutoCAD `.acb` files.
|
||||
//!
|
||||
//! An `.acb` is XML: a `bookName` and `colorPage`s of `colorEntry`s, each a
|
||||
//! `colorName` and an `RGB8` value. Books from some vendors encrypt their
|
||||
//! values; those cannot be read.
|
||||
|
||||
/// One named colour.
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct BookColor {
|
||||
pub name: String,
|
||||
pub rgb: (u8, u8, u8),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct ColorBook {
|
||||
pub name: String,
|
||||
/// Every colour, page after page.
|
||||
pub colors: Vec<BookColor>,
|
||||
/// Where each page starts in `colors`.
|
||||
page_starts: Vec<usize>,
|
||||
}
|
||||
|
||||
/// Pages for a book without `colorPage`s, as AutoCAD shows them.
|
||||
const PAGE_SIZE: usize = 7;
|
||||
|
||||
impl ColorBook {
|
||||
pub fn page_count(&self) -> usize {
|
||||
self.page_starts.len()
|
||||
}
|
||||
|
||||
/// The colours of one page, and the index of its first colour.
|
||||
pub fn page(&self, page: usize) -> (usize, &[BookColor]) {
|
||||
let start = self.page_starts.get(page).copied().unwrap_or(0);
|
||||
let end = self
|
||||
.page_starts
|
||||
.get(page + 1)
|
||||
.copied()
|
||||
.unwrap_or(self.colors.len());
|
||||
(start, &self.colors[start..end])
|
||||
}
|
||||
|
||||
/// The page holding colour `index`.
|
||||
pub fn page_of(&self, index: usize) -> usize {
|
||||
self.page_starts
|
||||
.partition_point(|&start| start <= index)
|
||||
.saturating_sub(1)
|
||||
}
|
||||
|
||||
/// The colour with this name, as stored after the `$` of `BOOK$NAME`.
|
||||
pub fn find(&self, name: &str) -> Option<usize> {
|
||||
self.colors.iter().position(|c| c.name == name)
|
||||
}
|
||||
|
||||
/// The `BOOK$NAME` identity objects and layers store.
|
||||
pub fn identity(&self, index: usize) -> String {
|
||||
format!("{}${}", self.name, self.colors[index].name)
|
||||
}
|
||||
}
|
||||
|
||||
/// Add `book`, replacing one of the same name; returns where it now is.
|
||||
pub fn add_or_replace(books: &mut Vec<ColorBook>, book: ColorBook) -> usize {
|
||||
match books.iter().position(|b| b.name == book.name) {
|
||||
Some(index) => {
|
||||
books[index] = book;
|
||||
index
|
||||
}
|
||||
None => {
|
||||
books.push(book);
|
||||
books.len() - 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The colour book that ships with the program; its licence is in the
|
||||
/// file's header comment.
|
||||
pub fn builtin() -> impl Iterator<Item = ColorBook> {
|
||||
parse(include_str!("../../../assets/color_books/ral-classic.acb")).into_iter()
|
||||
}
|
||||
|
||||
/// Where colour books are kept: `color_books` in the config folder. The
|
||||
/// web has no folders.
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub fn folder() -> Option<std::path::PathBuf> {
|
||||
crate::config::config_dir().map(|dir| dir.join("color_books"))
|
||||
}
|
||||
|
||||
/// The built-in book plus every `.acb` file in the colour book folder, in
|
||||
/// file-name order. A book replaces an earlier one of the same name.
|
||||
pub fn read_all() -> Vec<ColorBook> {
|
||||
#[cfg_attr(target_arch = "wasm32", allow(unused_mut))]
|
||||
let mut books: Vec<_> = builtin().collect();
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
if let Some(dir) = folder() {
|
||||
let mut files: Vec<_> = std::fs::read_dir(dir)
|
||||
.into_iter()
|
||||
.flatten()
|
||||
.filter_map(|entry| entry.ok().map(|entry| entry.path()))
|
||||
.filter(|path| {
|
||||
path.extension()
|
||||
.is_some_and(|ext| ext.eq_ignore_ascii_case("acb"))
|
||||
})
|
||||
.collect();
|
||||
files.sort();
|
||||
for book in files
|
||||
.into_iter()
|
||||
.filter_map(|path| std::fs::read_to_string(path).ok())
|
||||
.filter_map(|text| parse(&text).ok())
|
||||
{
|
||||
add_or_replace(&mut books, book);
|
||||
}
|
||||
}
|
||||
books
|
||||
}
|
||||
|
||||
/// Ask for an `.acb` file and read it. On desktop the file is copied into
|
||||
/// the colour book folder, so it stays when the original moves; the web
|
||||
/// keeps the book for the session only.
|
||||
pub async fn load_from_dialog() -> Result<ColorBook, String> {
|
||||
let file = crate::sys::file_dialog()
|
||||
.add_filter(crate::t!("Color book").as_ref(), &["acb", "ACB"])
|
||||
.pick_file()
|
||||
.await
|
||||
.ok_or_else(|| "Cancelled".to_string())?;
|
||||
let bytes = file.read().await;
|
||||
let book = parse(&String::from_utf8_lossy(&bytes))?;
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
if let Some(folder) = folder() {
|
||||
std::fs::create_dir_all(&folder)
|
||||
.and_then(|()| std::fs::write(folder.join(file.file_name()), &bytes))
|
||||
.map_err(|e| e.to_string())?;
|
||||
}
|
||||
Ok(book)
|
||||
}
|
||||
|
||||
/// Read an `.acb` file's text.
|
||||
pub fn parse(text: &str) -> Result<ColorBook, String> {
|
||||
let doc = roxmltree::Document::parse(text.trim_start_matches('\u{feff}'))
|
||||
.map_err(|e| e.to_string())?;
|
||||
let root = doc.root_element();
|
||||
if !root.has_tag_name("colorBook") {
|
||||
return Err(crate::t!("Not a color book").into_owned());
|
||||
}
|
||||
let mut book = ColorBook {
|
||||
name: text_of(root, "bookName").unwrap_or_default().to_string(),
|
||||
colors: Vec::new(),
|
||||
page_starts: Vec::new(),
|
||||
};
|
||||
let mut encrypted = false;
|
||||
let mut read_entries = |parent: roxmltree::Node<'_, '_>, book: &mut ColorBook| {
|
||||
for entry in parent.children().filter(|n| n.has_tag_name("colorEntry")) {
|
||||
let rgb = child(entry, "RGB8").and_then(|rgb| {
|
||||
let channel = |tag| text_of(rgb, tag)?.parse::<u8>().ok();
|
||||
Some((channel("red")?, channel("green")?, channel("blue")?))
|
||||
});
|
||||
match (text_of(entry, "colorName"), rgb) {
|
||||
(Some(name), Some(rgb)) => book.colors.push(BookColor {
|
||||
name: name.to_string(),
|
||||
rgb,
|
||||
}),
|
||||
_ => encrypted = true,
|
||||
}
|
||||
}
|
||||
};
|
||||
let mut pages = root
|
||||
.children()
|
||||
.filter(|n| n.has_tag_name("colorPage"))
|
||||
.peekable();
|
||||
if pages.peek().is_some() {
|
||||
for page in pages {
|
||||
let start = book.colors.len();
|
||||
read_entries(page, &mut book);
|
||||
if book.colors.len() > start {
|
||||
book.page_starts.push(start);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
read_entries(root, &mut book);
|
||||
book.page_starts = (0..book.colors.len()).step_by(PAGE_SIZE).collect();
|
||||
}
|
||||
if book.colors.is_empty() {
|
||||
return Err(if encrypted {
|
||||
crate::t!("This color book is encrypted and cannot be read")
|
||||
} else {
|
||||
crate::t!("The color book has no colors")
|
||||
}
|
||||
.into_owned());
|
||||
}
|
||||
Ok(book)
|
||||
}
|
||||
|
||||
fn child<'a, 'i>(node: roxmltree::Node<'a, 'i>, tag: &str) -> Option<roxmltree::Node<'a, 'i>> {
|
||||
node.children().find(|n| n.has_tag_name(tag))
|
||||
}
|
||||
|
||||
fn text_of<'a>(node: roxmltree::Node<'a, '_>, tag: &str) -> Option<&'a str> {
|
||||
child(node, tag).and_then(|n| n.text()).map(str::trim)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
fn entry(name: &str, (r, g, b): (u8, u8, u8)) -> String {
|
||||
format!(
|
||||
"<colorEntry><colorName>{name}</colorName>\
|
||||
<RGB8><red>{r}</red><green>{g}</green><blue>{b}</blue></RGB8></colorEntry>"
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn reads_pages_and_colours() {
|
||||
let text = format!(
|
||||
"\u{feff}<?xml version=\"1.0\"?><colorBook><bookName>RAL CLASSIC</bookName>\
|
||||
<colorPage><pageColor><RGB8><red>1</red><green>2</green><blue>3</blue></RGB8></pageColor>{}{}</colorPage>\
|
||||
<colorPage>{}</colorPage></colorBook>",
|
||||
entry("RAL 1000", (190, 189, 127)),
|
||||
entry("RAL 1001", (194, 176, 120)),
|
||||
entry("RAL 5015", (34, 113, 179)),
|
||||
);
|
||||
let book = parse(&text).unwrap();
|
||||
assert_eq!(book.name, "RAL CLASSIC");
|
||||
assert_eq!(book.page_count(), 2);
|
||||
assert_eq!(book.page(1), (2, &book.colors[2..]));
|
||||
assert_eq!(book.page_of(1), 0);
|
||||
assert_eq!(book.page_of(2), 1);
|
||||
let blue = book.find("RAL 5015").unwrap();
|
||||
assert_eq!(book.colors[blue].rgb, (34, 113, 179));
|
||||
assert_eq!(book.identity(blue), "RAL CLASSIC$RAL 5015");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_book_without_pages_gets_pages_of_seven() {
|
||||
let entries: String = (0..9).map(|i| entry(&format!("C{i}"), (i, i, i))).collect();
|
||||
let book = parse(&format!(
|
||||
"<colorBook><bookName>B</bookName>{entries}</colorBook>"
|
||||
))
|
||||
.unwrap();
|
||||
assert_eq!(book.page_count(), 2);
|
||||
assert_eq!(book.page(1).1.len(), 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rejects_unreadable_books() {
|
||||
assert!(parse("not xml").is_err());
|
||||
assert!(parse("<palette/>").is_err());
|
||||
let encrypted = "<colorBook><bookName>P</bookName><colorPage><colorEntry>\
|
||||
<colorName>X</colorName><RGB8Encrypt>AbC</RGB8Encrypt></colorEntry>\
|
||||
</colorPage></colorBook>";
|
||||
assert!(parse(encrypted).is_err());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,247 @@
|
||||
//! The colours a drawing uses, for the Document tab.
|
||||
//!
|
||||
//! One pass over every object (block definitions included) and every layer
|
||||
//! when the panel opens. Keys borrow from the drawing, so the pass allocates
|
||||
//! only per distinct colour, never per object.
|
||||
|
||||
use super::math;
|
||||
use codec::types::{Color as AcadColor, Handle};
|
||||
use rustc_hash::FxHashMap;
|
||||
|
||||
/// One distinct colour: a concrete colour plus, for a colour-book colour,
|
||||
/// its `BOOK$NAME` identity.
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct DocColor {
|
||||
pub color: AcadColor,
|
||||
pub name: Option<String>,
|
||||
/// Objects and layers using it. Only used for sorting, never shown.
|
||||
uses: u32,
|
||||
/// The newest object or layer using it.
|
||||
newest: Handle,
|
||||
}
|
||||
|
||||
impl DocColor {
|
||||
/// The name in the list view: the colour-book colour's own name (without
|
||||
/// the book), `ACI n` or `#RRGGBB`.
|
||||
pub fn label(&self) -> String {
|
||||
match &self.name {
|
||||
Some(name) => name
|
||||
.split_once('$')
|
||||
.map_or(name.as_str(), |(_, name)| name)
|
||||
.to_string(),
|
||||
None => math::short_name(self.color),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Swatches or a list of named bars.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
|
||||
pub enum DocView {
|
||||
#[default]
|
||||
Grid,
|
||||
List,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
|
||||
pub enum DocSort {
|
||||
#[default]
|
||||
Recent,
|
||||
Usage,
|
||||
Hue,
|
||||
Number,
|
||||
NameAsc,
|
||||
NameDesc,
|
||||
}
|
||||
|
||||
impl DocSort {
|
||||
pub const ALL: [DocSort; 6] = [
|
||||
DocSort::Recent,
|
||||
DocSort::Usage,
|
||||
DocSort::Hue,
|
||||
DocSort::Number,
|
||||
DocSort::NameAsc,
|
||||
DocSort::NameDesc,
|
||||
];
|
||||
}
|
||||
|
||||
impl std::fmt::Display for DocSort {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.write_str(&match self {
|
||||
DocSort::Recent => crate::t!("Recently used"),
|
||||
DocSort::Usage => crate::t!("Most used"),
|
||||
DocSort::Hue => crate::t!("Color"),
|
||||
DocSort::Number => crate::t!("Color number"),
|
||||
DocSort::NameAsc => crate::t!("Name A–Z"),
|
||||
DocSort::NameDesc => crate::t!("Name Z–A"),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// A colour as a hash key: index colours keep their number, true colours
|
||||
/// sit above 255.
|
||||
fn key(color: AcadColor) -> Option<u32> {
|
||||
match color {
|
||||
AcadColor::Index(i) => Some(u32::from(i)),
|
||||
AcadColor::Rgb { r, g, b } => {
|
||||
Some(1 << 24 | u32::from(r) << 16 | u32::from(g) << 8 | u32::from(b))
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// A layer's colour-book identity, `BOOK$NAME`. Layers keep the book and
|
||||
/// the colour name apart; objects store them joined like this.
|
||||
pub fn layer_identity(layer: &codec::tables::Layer) -> Option<String> {
|
||||
match (&layer.book_name, &layer.color_name) {
|
||||
(Some(book), Some(name)) => Some(format!("{book}${name}")),
|
||||
(None, Some(name)) => Some(name.clone()),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Every concrete colour in the drawing. ByLayer, ByBlock and None are
|
||||
/// states rather than colours and are skipped.
|
||||
pub fn collect(document: &codec::CadDocument) -> Vec<DocColor> {
|
||||
let layer_names: Vec<Option<String>> = document.layers.iter().map(layer_identity).collect();
|
||||
let objects = document.entities().map(|entity| {
|
||||
let common = entity.common();
|
||||
(common.color, common.color_name.as_deref(), common.handle)
|
||||
});
|
||||
let layers = document
|
||||
.layers
|
||||
.iter()
|
||||
.zip(&layer_names)
|
||||
.map(|(layer, name)| (layer.color, name.as_deref(), layer.handle));
|
||||
tally(objects.chain(layers))
|
||||
}
|
||||
|
||||
fn tally<'a>(uses: impl Iterator<Item = (AcadColor, Option<&'a str>, Handle)>) -> Vec<DocColor> {
|
||||
let mut index: FxHashMap<(u32, Option<&str>), usize> = FxHashMap::default();
|
||||
let mut colors: Vec<DocColor> = Vec::new();
|
||||
for (color, name, handle) in uses {
|
||||
let Some(k) = key(color) else { continue };
|
||||
let name = name.filter(|name| !name.is_empty());
|
||||
let slot = *index.entry((k, name)).or_insert_with(|| {
|
||||
colors.push(DocColor {
|
||||
color,
|
||||
name: name.map(str::to_string),
|
||||
uses: 0,
|
||||
newest: handle,
|
||||
});
|
||||
colors.len() - 1
|
||||
});
|
||||
let entry = &mut colors[slot];
|
||||
entry.uses += 1;
|
||||
entry.newest = entry.newest.max(handle);
|
||||
}
|
||||
colors
|
||||
}
|
||||
|
||||
/// Sort for display. `recent` is this drawing's pick history, newest first;
|
||||
/// colours not picked this session follow, newest object first.
|
||||
pub fn sort(colors: &mut [DocColor], by: DocSort, recent: &[AcadColor]) {
|
||||
match by {
|
||||
// The rank is looked up once per colour, not on every comparison.
|
||||
DocSort::Recent => colors.sort_by_cached_key(|c| {
|
||||
let rank = recent
|
||||
.iter()
|
||||
.position(|&r| r == c.color)
|
||||
.unwrap_or(usize::MAX);
|
||||
(rank, std::cmp::Reverse(c.newest))
|
||||
}),
|
||||
DocSort::Usage => colors.sort_by(|a, b| b.uses.cmp(&a.uses).then(b.newest.cmp(&a.newest))),
|
||||
DocSort::Hue => colors.sort_by_cached_key(|c| {
|
||||
let (r, g, b) = math::color_rgb(c.color).unwrap_or_default();
|
||||
let hsb = math::rgb_to_hsb(r, g, b);
|
||||
// Grays after the colours; within a hue band, light to dark.
|
||||
let gray = hsb.s < 0.12 || hsb.b < 0.08;
|
||||
let band = if gray {
|
||||
0
|
||||
} else {
|
||||
(hsb.h / 15.0).round() as u32
|
||||
};
|
||||
(gray, band, std::cmp::Reverse((hsb.b * 1000.0) as u32))
|
||||
}),
|
||||
DocSort::Number => colors.sort_by(|a, b| {
|
||||
let kind = |c: &DocColor| match (&c.name, c.color) {
|
||||
(None, AcadColor::Index(_)) => 0,
|
||||
(None, _) => 1,
|
||||
_ => 2,
|
||||
};
|
||||
kind(a)
|
||||
.cmp(&kind(b))
|
||||
.then_with(|| a.name.cmp(&b.name))
|
||||
.then_with(|| key(a.color).cmp(&key(b.color)))
|
||||
}),
|
||||
DocSort::NameAsc => colors.sort_by_cached_key(|c| c.label().to_lowercase()),
|
||||
DocSort::NameDesc => {
|
||||
colors.sort_by_cached_key(|c| std::cmp::Reverse(c.label().to_lowercase()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
const RED: AcadColor = AcadColor::Index(1);
|
||||
const BLUE: AcadColor = AcadColor::Index(5);
|
||||
const SKY: AcadColor = AcadColor::Rgb {
|
||||
r: 57,
|
||||
g: 191,
|
||||
b: 252,
|
||||
};
|
||||
|
||||
fn sample() -> Vec<DocColor> {
|
||||
let h = Handle::new;
|
||||
tally(
|
||||
[
|
||||
(RED, None, h(1)),
|
||||
(RED, None, h(2)),
|
||||
(RED, None, h(3)),
|
||||
(AcadColor::ByLayer, None, h(4)),
|
||||
(SKY, None, h(5)),
|
||||
(SKY, Some("RAL CLASSIC$RAL 5015"), h(6)),
|
||||
(BLUE, Some(""), h(7)),
|
||||
]
|
||||
.into_iter(),
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn collects_each_concrete_colour_once() {
|
||||
let colors = sample();
|
||||
assert_eq!(colors.len(), 4, "ByLayer skipped, book colour kept apart");
|
||||
assert_eq!(colors[0].uses, 3);
|
||||
assert_eq!(colors[0].newest, Handle::new(3));
|
||||
assert_eq!(colors[3].name, None, "an empty name is no name");
|
||||
assert_eq!(colors[2].label(), "RAL 5015");
|
||||
assert_eq!(colors[0].label(), "ACI 1");
|
||||
assert_eq!(colors[1].label(), "#39BFFC");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sorts_four_ways() {
|
||||
let order = |by, recent: &[AcadColor]| {
|
||||
let mut colors = sample();
|
||||
sort(&mut colors, by, recent);
|
||||
colors
|
||||
.iter()
|
||||
.map(|c| (c.color, c.name.is_some()))
|
||||
.collect::<Vec<_>>()
|
||||
};
|
||||
// Picked this session first, then newest object first.
|
||||
assert_eq!(
|
||||
order(DocSort::Recent, &[RED]),
|
||||
[(RED, false), (BLUE, false), (SKY, true), (SKY, false)]
|
||||
);
|
||||
assert_eq!(order(DocSort::Usage, &[])[0], (RED, false));
|
||||
assert_eq!(
|
||||
order(DocSort::Number, &[]),
|
||||
[(RED, false), (BLUE, false), (SKY, false), (SKY, true)]
|
||||
);
|
||||
// Red (hue 0) before sky blue (hue ~199) before blue (hue 240).
|
||||
assert_eq!(order(DocSort::Hue, &[])[0], (RED, false));
|
||||
assert_eq!(order(DocSort::Hue, &[])[3], (BLUE, false));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,506 @@
|
||||
//! The colour editor: RGB, HSB or CMYK sliders above a saturation/brightness
|
||||
//! field with a hue strip, plus a hex box. Shared by the CAD Select Color
|
||||
//! panel and the interface colour picker.
|
||||
|
||||
use super::math::{self, Hsb};
|
||||
use super::widgets::ColorArea;
|
||||
use super::ColorPickerMsg;
|
||||
use crate::app::Message;
|
||||
use crate::t;
|
||||
use iced::widget::{
|
||||
button, column, container, row, slider, stack, text, text_input, vertical_slider, Space,
|
||||
};
|
||||
use iced::{Background, Border, Color, Element, Gradient, Length, Radians, Theme};
|
||||
|
||||
const ROW_HEIGHT: f32 = 26.0;
|
||||
const TRACK: f32 = 18.0;
|
||||
const STRIP_WIDTH: f32 = 22.0;
|
||||
const ROW_GAP: f32 = 6.0;
|
||||
/// The field's height in the interface picker, which has no fixed height.
|
||||
const FIELD_HEIGHT: f32 = 96.0;
|
||||
const MODE_WIDTH: f32 = 48.0;
|
||||
const MODE_HEIGHT: f32 = 24.0;
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
|
||||
pub enum EditorMode {
|
||||
#[default]
|
||||
Rgb,
|
||||
Hsb,
|
||||
Cmyk,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum EditorMsg {
|
||||
Mode(EditorMode),
|
||||
Hue(f32),
|
||||
/// Saturation in percent.
|
||||
Saturation(f32),
|
||||
/// Brightness in percent.
|
||||
Brightness(f32),
|
||||
/// Saturation and brightness from the field, both 0–1.
|
||||
Field(f32, f32),
|
||||
Red(u8),
|
||||
Green(u8),
|
||||
Blue(u8),
|
||||
/// One CMYK channel (0 cyan … 3 black), in percent.
|
||||
Cmyk(usize, f32),
|
||||
HexInput(String),
|
||||
HexSubmit,
|
||||
}
|
||||
|
||||
pub struct ColorEditor {
|
||||
mode: EditorMode,
|
||||
/// Kept beside `rgb` so hue and saturation survive grays and black.
|
||||
hsb: Hsb,
|
||||
/// Kept too, so black keeps its inks and rounding doesn't move them.
|
||||
cmyk: [f32; 4],
|
||||
rgb: (u8, u8, u8),
|
||||
hex_draft: Option<String>,
|
||||
}
|
||||
|
||||
impl ColorEditor {
|
||||
pub fn new(rgb: (u8, u8, u8)) -> Self {
|
||||
let mut editor = Self {
|
||||
mode: EditorMode::default(),
|
||||
hsb: Hsb {
|
||||
h: 0.0,
|
||||
s: 0.0,
|
||||
b: 1.0,
|
||||
},
|
||||
cmyk: [0.0; 4],
|
||||
rgb,
|
||||
hex_draft: None,
|
||||
};
|
||||
editor.set_rgb(rgb);
|
||||
editor
|
||||
}
|
||||
|
||||
pub fn rgb(&self) -> (u8, u8, u8) {
|
||||
self.rgb
|
||||
}
|
||||
|
||||
pub fn set_rgb(&mut self, (r, g, b): (u8, u8, u8)) {
|
||||
let next = math::rgb_to_hsb(r, g, b);
|
||||
// A gray has no hue and black no saturation; keep the previous ones
|
||||
// so the sliders and field don't jump.
|
||||
let gray = next.s == 0.0 || next.b == 0.0;
|
||||
self.hsb = Hsb {
|
||||
h: if gray { self.hsb.h } else { next.h },
|
||||
s: if next.b == 0.0 { self.hsb.s } else { next.s },
|
||||
b: next.b,
|
||||
};
|
||||
self.cmyk = math::rgb_to_cmyk(r, g, b);
|
||||
self.rgb = (r, g, b);
|
||||
self.hex_draft = None;
|
||||
}
|
||||
|
||||
fn set_hsb(&mut self, hsb: Hsb) {
|
||||
self.hsb = Hsb {
|
||||
h: hsb.h.clamp(0.0, 360.0),
|
||||
s: hsb.s.clamp(0.0, 1.0),
|
||||
b: hsb.b.clamp(0.0, 1.0),
|
||||
};
|
||||
self.rgb = self.hsb.to_rgb();
|
||||
self.cmyk = math::rgb_to_cmyk(self.rgb.0, self.rgb.1, self.rgb.2);
|
||||
self.hex_draft = None;
|
||||
}
|
||||
|
||||
fn set_cmyk(&mut self, channel: usize, value: f32) {
|
||||
let mut cmyk = self.cmyk;
|
||||
cmyk[channel.min(3)] = value.clamp(0.0, 100.0);
|
||||
self.set_rgb(math::cmyk_to_rgb(cmyk));
|
||||
self.cmyk = cmyk;
|
||||
}
|
||||
|
||||
/// Apply a message; returns whether the colour changed.
|
||||
pub fn update(&mut self, msg: EditorMsg) -> bool {
|
||||
let before = self.rgb;
|
||||
let (r, g, b) = self.rgb;
|
||||
match msg {
|
||||
EditorMsg::Mode(mode) => self.mode = mode,
|
||||
EditorMsg::Hue(h) => self.set_hsb(Hsb { h, ..self.hsb }),
|
||||
EditorMsg::Saturation(s) => self.set_hsb(Hsb {
|
||||
s: s / 100.0,
|
||||
..self.hsb
|
||||
}),
|
||||
EditorMsg::Brightness(v) => self.set_hsb(Hsb {
|
||||
b: v / 100.0,
|
||||
..self.hsb
|
||||
}),
|
||||
EditorMsg::Field(s, b) => self.set_hsb(Hsb { s, b, ..self.hsb }),
|
||||
EditorMsg::Red(v) => self.set_rgb((v, g, b)),
|
||||
EditorMsg::Green(v) => self.set_rgb((r, v, b)),
|
||||
EditorMsg::Blue(v) => self.set_rgb((r, g, v)),
|
||||
EditorMsg::Cmyk(channel, v) => self.set_cmyk(channel, v),
|
||||
EditorMsg::HexInput(value) => {
|
||||
// Six digits are unambiguous, so apply them while typing; the
|
||||
// short #RGB form waits for Enter.
|
||||
let complete = value.trim().trim_start_matches('#').len() == 6;
|
||||
if let Some(rgb) = math::parse_hex(&value).filter(|_| complete) {
|
||||
self.set_rgb(rgb);
|
||||
}
|
||||
self.hex_draft = Some(value);
|
||||
}
|
||||
EditorMsg::HexSubmit => {
|
||||
if let Some(rgb) = self.hex_draft.take().and_then(|d| math::parse_hex(&d)) {
|
||||
self.set_rgb(rgb);
|
||||
}
|
||||
}
|
||||
}
|
||||
self.rgb != before
|
||||
}
|
||||
|
||||
/// `mode_end` sits at the far right of the mode buttons (the pipette);
|
||||
/// `hex_end` at the far right of the hex row (the nearest index colour).
|
||||
/// With `fill` the field takes all the height the sliders leave, so the
|
||||
/// hex row stays at the bottom and CMYK's fourth slider only makes the
|
||||
/// field a little lower.
|
||||
pub fn view<'a>(
|
||||
&'a self,
|
||||
mode_end: Option<Element<'a, Message>>,
|
||||
hex_end: Option<Element<'a, Message>>,
|
||||
fill: bool,
|
||||
) -> Element<'a, Message> {
|
||||
let height = if fill {
|
||||
Length::Fill
|
||||
} else {
|
||||
Length::Fixed(FIELD_HEIGHT)
|
||||
};
|
||||
column![
|
||||
self.mode_row(mode_end),
|
||||
self.slider_rows(),
|
||||
self.field(height),
|
||||
self.hex_row(hex_end),
|
||||
]
|
||||
.spacing(12)
|
||||
.into()
|
||||
}
|
||||
|
||||
/// RGB, HSB and CMYK, with `end` at the far right.
|
||||
fn mode_row<'a>(&self, end: Option<Element<'a, Message>>) -> Element<'a, Message> {
|
||||
let mode_button = |label: &'static str, mode: EditorMode| {
|
||||
let active = self.mode == mode;
|
||||
button(container(text(label).size(13)).center(Length::Fill))
|
||||
.on_press(msg(EditorMsg::Mode(mode)))
|
||||
.width(MODE_WIDTH)
|
||||
.height(MODE_HEIGHT)
|
||||
.padding(0)
|
||||
.style(move |theme: &Theme, status| {
|
||||
super::picker::toggle_style(theme, active, status)
|
||||
})
|
||||
};
|
||||
let mut modes = row![
|
||||
mode_button("RGB", EditorMode::Rgb),
|
||||
mode_button("HSB", EditorMode::Hsb),
|
||||
mode_button("CMYK", EditorMode::Cmyk),
|
||||
Space::new().width(Length::Fill),
|
||||
]
|
||||
.spacing(6);
|
||||
if let Some(end) = end {
|
||||
modes = modes.push(end);
|
||||
}
|
||||
modes.into()
|
||||
}
|
||||
|
||||
/// Gradient sliders: red, green and blue; hue, saturation and
|
||||
/// brightness; or cyan, magenta, yellow and black.
|
||||
fn slider_rows<'a>(&self) -> Element<'a, Message> {
|
||||
let Hsb { h, s, b } = self.hsb;
|
||||
let (r, g, bl) = self.rgb;
|
||||
let rows = if self.mode == EditorMode::Cmyk {
|
||||
let ink = |i: usize, letter| {
|
||||
let at = |v| {
|
||||
let mut cmyk = self.cmyk;
|
||||
cmyk[i] = v;
|
||||
rgb_color(math::cmyk_to_rgb(cmyk))
|
||||
};
|
||||
channel(
|
||||
letter,
|
||||
self.cmyk[i],
|
||||
100.0,
|
||||
Some('%'),
|
||||
vec![(0.0, at(0.0)), (1.0, at(100.0))],
|
||||
move |v| EditorMsg::Cmyk(i, v),
|
||||
)
|
||||
};
|
||||
column![ink(0, "C"), ink(1, "M"), ink(2, "Y"), ink(3, "K")]
|
||||
} else if self.mode == EditorMode::Rgb {
|
||||
let ends = |lo: (u8, u8, u8), hi: (u8, u8, u8)| {
|
||||
vec![(0.0, rgb_color(lo)), (1.0, rgb_color(hi))]
|
||||
};
|
||||
let byte = |f: fn(u8) -> EditorMsg| move |v: f32| f(v.round() as u8);
|
||||
column![
|
||||
channel(
|
||||
"R",
|
||||
f32::from(r),
|
||||
255.0,
|
||||
None,
|
||||
ends((0, g, bl), (255, g, bl)),
|
||||
byte(EditorMsg::Red)
|
||||
),
|
||||
channel(
|
||||
"G",
|
||||
f32::from(g),
|
||||
255.0,
|
||||
None,
|
||||
ends((r, 0, bl), (r, 255, bl)),
|
||||
byte(EditorMsg::Green)
|
||||
),
|
||||
channel(
|
||||
"B",
|
||||
f32::from(bl),
|
||||
255.0,
|
||||
None,
|
||||
ends((r, g, 0), (r, g, 255)),
|
||||
byte(EditorMsg::Blue)
|
||||
),
|
||||
]
|
||||
} else {
|
||||
column![
|
||||
channel("H", h, 360.0, Some('°'), hue_stops(), EditorMsg::Hue),
|
||||
channel(
|
||||
"S",
|
||||
s * 100.0,
|
||||
100.0,
|
||||
Some('%'),
|
||||
vec![(0.0, hsb_color(h, 0.0, b)), (1.0, hsb_color(h, 1.0, b))],
|
||||
EditorMsg::Saturation,
|
||||
),
|
||||
channel(
|
||||
"B",
|
||||
b * 100.0,
|
||||
100.0,
|
||||
Some('%'),
|
||||
vec![(0.0, Color::BLACK), (1.0, hsb_color(h, s, 1.0))],
|
||||
EditorMsg::Brightness,
|
||||
),
|
||||
]
|
||||
};
|
||||
rows.spacing(ROW_GAP).into()
|
||||
}
|
||||
|
||||
/// The saturation/brightness field with its hue strip.
|
||||
fn field<'a>(&self, height: Length) -> Element<'a, Message> {
|
||||
let Hsb { h, s, b } = self.hsb;
|
||||
let area = ColorArea::new(hsb_color(h, 1.0, 1.0), rgb_color(self.rgb), s, b, |s, b| {
|
||||
msg(EditorMsg::Field(s, b))
|
||||
})
|
||||
.height(height);
|
||||
// A vertical slider grows upwards; the strip runs red at the top.
|
||||
let strip = stack![
|
||||
container(Space::new())
|
||||
.width(STRIP_WIDTH)
|
||||
.height(height)
|
||||
.style(|_theme: &Theme| gradient_box(std::f32::consts::PI, &hue_stops())),
|
||||
vertical_slider(0.0..=360.0, 360.0 - h, |v| msg(EditorMsg::Hue(360.0 - v)))
|
||||
.width(STRIP_WIDTH)
|
||||
.height(height)
|
||||
.style(handle_style),
|
||||
];
|
||||
row![area, strip].spacing(12).into()
|
||||
}
|
||||
|
||||
/// The hex value with `end` (the nearest index colour) at the far right.
|
||||
fn hex_row<'a>(&self, end: Option<Element<'a, Message>>) -> Element<'a, Message> {
|
||||
let (r, g, b) = self.rgb;
|
||||
let hex = self
|
||||
.hex_draft
|
||||
.clone()
|
||||
.unwrap_or_else(|| math::hex_string(r, g, b));
|
||||
let mut hex_row = row![
|
||||
text(t!("Hex")).size(11).style(super::picker::muted_text),
|
||||
text_input("", &hex)
|
||||
.on_input(|value| msg(EditorMsg::HexInput(value)))
|
||||
.on_submit(msg(EditorMsg::HexSubmit))
|
||||
.size(12)
|
||||
.font(iced::Font::MONOSPACE)
|
||||
.padding([5, 6])
|
||||
.width(Length::Fixed(74.0)),
|
||||
Space::new().width(Length::Fill),
|
||||
]
|
||||
.spacing(8)
|
||||
.align_y(iced::Center);
|
||||
if let Some(end) = end {
|
||||
hex_row = hex_row.push(end);
|
||||
}
|
||||
hex_row.into()
|
||||
}
|
||||
}
|
||||
|
||||
fn msg(editor: EditorMsg) -> Message {
|
||||
Message::ColorPicker(ColorPickerMsg::Editor(editor))
|
||||
}
|
||||
|
||||
/// One labelled channel: a gradient slider and its number box.
|
||||
fn channel<'a>(
|
||||
letter: &'static str,
|
||||
value: f32,
|
||||
max: f32,
|
||||
unit: Option<char>,
|
||||
stops: Vec<(f32, Color)>,
|
||||
on_change: impl Fn(f32) -> EditorMsg + Copy + 'static,
|
||||
) -> Element<'a, Message> {
|
||||
// iced's slider splits its own rail at the handle, which would squeeze a
|
||||
// gradient; draw the gradient underneath and keep the rail transparent.
|
||||
let track = container(
|
||||
container(Space::new())
|
||||
.width(Length::Fill)
|
||||
.height(TRACK)
|
||||
.style(move |_theme: &Theme| gradient_box(std::f32::consts::FRAC_PI_2, &stops)),
|
||||
)
|
||||
.height(ROW_HEIGHT)
|
||||
.center_y(ROW_HEIGHT);
|
||||
row![
|
||||
container(text(letter).size(12).font(iced::Font::MONOSPACE))
|
||||
.width(12)
|
||||
.align_x(iced::Center),
|
||||
stack![
|
||||
track,
|
||||
slider(0.0..=max, value, move |v| msg(on_change(v)))
|
||||
.height(ROW_HEIGHT)
|
||||
.shift_step(10.0)
|
||||
.style(handle_style),
|
||||
],
|
||||
number_box(value, max, unit, on_change),
|
||||
]
|
||||
.spacing(8)
|
||||
.align_y(iced::Center)
|
||||
.into()
|
||||
}
|
||||
|
||||
/// A compact spin box with the unit drawn inside, after the digits.
|
||||
fn number_box(
|
||||
value: f32,
|
||||
max: f32,
|
||||
unit: Option<char>,
|
||||
on_change: impl Fn(f32) -> EditorMsg + Copy + 'static,
|
||||
) -> Element<'static, Message> {
|
||||
let input =
|
||||
iced_aw::widget::NumberInput::new(&(value.round() as u16), 0..=max as u16, move |v| {
|
||||
msg(on_change(f32::from(v)))
|
||||
})
|
||||
.set_size(12.0)
|
||||
.font(iced::Font::MONOSPACE)
|
||||
.width(Length::Fixed(54.0))
|
||||
// Padding under 5 would make NumberInput swap its arrows for + / −;
|
||||
// the extra right padding keeps the digits clear of the arrows.
|
||||
.padding(iced::Padding {
|
||||
top: 5.0,
|
||||
right: 18.0,
|
||||
bottom: 5.0,
|
||||
left: 2.0,
|
||||
})
|
||||
.align_x(iced::alignment::Horizontal::Right)
|
||||
.style(
|
||||
|theme: &Theme, _status| iced_aw::style::number_input::Style {
|
||||
button_background: None,
|
||||
icon_color: theme.palette().background.base.text.scale_alpha(0.75),
|
||||
},
|
||||
);
|
||||
match unit {
|
||||
Some(code_point) => input
|
||||
.icon(text_input::Icon {
|
||||
font: iced::Font::MONOSPACE,
|
||||
code_point,
|
||||
size: Some(iced::Pixels(12.0)),
|
||||
spacing: 1.0,
|
||||
side: text_input::Side::Right,
|
||||
})
|
||||
.into(),
|
||||
None => input.into(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Transparent rail with a white bar handle, for both slider directions.
|
||||
fn handle_style(_theme: &Theme, _status: slider::Status) -> slider::Style {
|
||||
slider::Style {
|
||||
rail: slider::Rail {
|
||||
backgrounds: (Color::TRANSPARENT.into(), Color::TRANSPARENT.into()),
|
||||
width: TRACK,
|
||||
border: Border::default(),
|
||||
},
|
||||
handle: slider::Handle {
|
||||
shape: slider::HandleShape::Rectangle {
|
||||
width: 8,
|
||||
border_radius: 3.0.into(),
|
||||
},
|
||||
background: Color::WHITE.into(),
|
||||
border_width: 1.0,
|
||||
border_color: Color::from_rgba(0.0, 0.0, 0.0, 0.4),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// A rounded box filled with a linear gradient. Angles run clockwise from
|
||||
/// "bottom to top": a quarter turn is left to right, a half turn top to bottom.
|
||||
fn gradient_box(angle: f32, stops: &[(f32, Color)]) -> container::Style {
|
||||
let linear = stops.iter().fold(
|
||||
iced::gradient::Linear::new(Radians(angle)),
|
||||
|linear, &(at, color)| linear.add_stop(at, color),
|
||||
);
|
||||
container::Style {
|
||||
background: Some(Background::Gradient(Gradient::Linear(linear))),
|
||||
border: Border {
|
||||
color: Color::from_rgba(0.0, 0.0, 0.0, 0.35),
|
||||
width: 1.0,
|
||||
radius: 5.0.into(),
|
||||
},
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
fn hue_stops() -> Vec<(f32, Color)> {
|
||||
(0..=6)
|
||||
.map(|step| {
|
||||
let at = step as f32 / 6.0;
|
||||
(at, hsb_color(at * 360.0, 1.0, 1.0))
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn hsb_color(h: f32, s: f32, b: f32) -> Color {
|
||||
rgb_color(math::hsb_to_rgb(h, s, b))
|
||||
}
|
||||
|
||||
pub(super) fn rgb_color((r, g, b): (u8, u8, u8)) -> Color {
|
||||
Color::from_rgb8(r, g, b)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn sliders_produce_colours() {
|
||||
let mut e = ColorEditor::new((255, 0, 0));
|
||||
assert!(e.update(EditorMsg::Hue(120.0)));
|
||||
assert_eq!(e.rgb(), (0, 255, 0));
|
||||
e.update(EditorMsg::Red(10));
|
||||
assert_eq!(e.rgb(), (10, 255, 0));
|
||||
assert!(!e.update(EditorMsg::Mode(EditorMode::Rgb)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hue_survives_a_trip_through_gray() {
|
||||
let mut e = ColorEditor::new((0, 0, 255));
|
||||
e.update(EditorMsg::Saturation(0.0));
|
||||
e.update(EditorMsg::Saturation(100.0));
|
||||
assert_eq!(e.rgb(), (0, 0, 255));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hex_applies_six_digits_while_typing() {
|
||||
let mut e = ColorEditor::new((0, 0, 0));
|
||||
assert!(!e.update(EditorMsg::HexInput("#39BF".into())));
|
||||
assert!(e.update(EditorMsg::HexInput("#39BFFC".into())));
|
||||
assert_eq!(e.rgb(), (57, 191, 252));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn short_hex_waits_for_enter() {
|
||||
let mut e = ColorEditor::new((0, 0, 0));
|
||||
assert!(!e.update(EditorMsg::HexInput("#3bf".into())));
|
||||
assert!(e.update(EditorMsg::HexSubmit));
|
||||
assert_eq!(e.rgb(), (0x33, 0xBB, 0xFF));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,184 @@
|
||||
//! Pick a colour from anywhere inside the application window.
|
||||
//!
|
||||
//! While active, a transparent layer covers the window with a crosshair. The
|
||||
//! first mouse move after the panel is hidden asks iced for a screenshot of
|
||||
//! the window; a magnifier beside the cursor shows the pixels around it from
|
||||
//! that screenshot, and a click picks the centre pixel.
|
||||
|
||||
use super::editor::rgb_color;
|
||||
use super::{math, ColorPickerMsg};
|
||||
use crate::app::Message;
|
||||
use iced::widget::{column, container, mouse_area, pin, row, stack, text, Space};
|
||||
use iced::window::Screenshot;
|
||||
use iced::{Border, Color, Element, Length, Point, Theme};
|
||||
|
||||
/// Pixels shown across the magnifier (odd, so one sits in the centre).
|
||||
const GRID: i32 = 11;
|
||||
const CELL: f32 = 10.0;
|
||||
/// Gap between the cursor and the magnifier.
|
||||
const OFFSET: f32 = 20.0;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum EyedropperMsg {
|
||||
Start,
|
||||
Move(Point),
|
||||
Shot(Screenshot),
|
||||
Pick,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Eyedropper {
|
||||
shot: Option<Screenshot>,
|
||||
requested: bool,
|
||||
cursor: Option<Point>,
|
||||
}
|
||||
|
||||
impl Eyedropper {
|
||||
/// Track the cursor. Returns true when the window screenshot should be
|
||||
/// requested now — the panel is gone from the screen by then.
|
||||
pub fn track(&mut self, cursor: Point) -> bool {
|
||||
self.cursor = Some(cursor);
|
||||
let request = self.shot.is_none() && !self.requested;
|
||||
self.requested = true;
|
||||
request
|
||||
}
|
||||
|
||||
pub fn set_shot(&mut self, shot: Screenshot) {
|
||||
self.shot = Some(shot);
|
||||
}
|
||||
|
||||
/// The pixel under the cursor, once the screenshot has arrived.
|
||||
pub fn picked(&self) -> Option<(u8, u8, u8)> {
|
||||
sample(self.shot.as_ref()?, self.cursor?, 0, 0)
|
||||
}
|
||||
|
||||
pub fn view(&self) -> Element<'_, Message> {
|
||||
let eye = |m| Message::ColorPicker(ColorPickerMsg::Eyedropper(m));
|
||||
let catcher = mouse_area(Space::new().width(Length::Fill).height(Length::Fill))
|
||||
.on_move(move |p| eye(EyedropperMsg::Move(p)))
|
||||
.on_press(eye(EyedropperMsg::Pick))
|
||||
.interaction(iced::mouse::Interaction::Crosshair);
|
||||
|
||||
let (Some(shot), Some(cursor)) = (self.shot.as_ref(), self.cursor) else {
|
||||
return catcher.into();
|
||||
};
|
||||
|
||||
let half = GRID / 2;
|
||||
let mut grid = column![];
|
||||
for dy in -half..=half {
|
||||
let mut line = row![];
|
||||
for dx in -half..=half {
|
||||
let fill = sample(shot, cursor, dx, dy).map_or(Color::TRANSPARENT, rgb_color);
|
||||
let centre = dx == 0 && dy == 0;
|
||||
line = line.push(container(Space::new()).width(CELL).height(CELL).style(
|
||||
move |_theme: &Theme| container::Style {
|
||||
background: Some(fill.into()),
|
||||
border: Border {
|
||||
color: if centre {
|
||||
Color::WHITE
|
||||
} else {
|
||||
Color::TRANSPARENT
|
||||
},
|
||||
width: if centre { 1.5 } else { 0.0 },
|
||||
radius: 0.0.into(),
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
));
|
||||
}
|
||||
grid = grid.push(line);
|
||||
}
|
||||
let (r, g, b) = self.picked().unwrap_or((0, 0, 0));
|
||||
let loupe = container(
|
||||
column![
|
||||
grid,
|
||||
text(math::hex_string(r, g, b))
|
||||
.size(11)
|
||||
.font(iced::Font::MONOSPACE),
|
||||
]
|
||||
.spacing(4)
|
||||
.align_x(iced::Center),
|
||||
)
|
||||
.padding(4)
|
||||
.style(|theme: &Theme| {
|
||||
let palette = theme.palette();
|
||||
container::Style {
|
||||
background: Some(palette.background.base.color.into()),
|
||||
text_color: Some(palette.background.base.text),
|
||||
border: Border {
|
||||
color: palette.background.strong.color,
|
||||
width: 1.0,
|
||||
radius: 6.0.into(),
|
||||
},
|
||||
..Default::default()
|
||||
}
|
||||
});
|
||||
|
||||
// Keep the magnifier on screen: flip it to the other side of the
|
||||
// cursor near the right and bottom edges.
|
||||
let size = GRID as f32 * CELL + 8.0;
|
||||
let window_w = shot.size.width as f32 / shot.scale_factor;
|
||||
let window_h = shot.size.height as f32 / shot.scale_factor;
|
||||
// In a very small window neither side fits; stay inside the top-left.
|
||||
let x = if cursor.x + OFFSET + size > window_w {
|
||||
cursor.x - OFFSET - size
|
||||
} else {
|
||||
cursor.x + OFFSET
|
||||
}
|
||||
.max(0.0);
|
||||
let y = if cursor.y + OFFSET + size + 20.0 > window_h {
|
||||
cursor.y - OFFSET - size - 20.0
|
||||
} else {
|
||||
cursor.y + OFFSET
|
||||
}
|
||||
.max(0.0);
|
||||
stack![catcher, pin(loupe).x(x).y(y)].into()
|
||||
}
|
||||
}
|
||||
|
||||
/// The screenshot pixel `dx`, `dy` physical pixels away from a logical point.
|
||||
fn sample(shot: &Screenshot, at: Point, dx: i32, dy: i32) -> Option<(u8, u8, u8)> {
|
||||
let x = (at.x * shot.scale_factor) as i64 + i64::from(dx);
|
||||
let y = (at.y * shot.scale_factor) as i64 + i64::from(dy);
|
||||
let (w, h) = (i64::from(shot.size.width), i64::from(shot.size.height));
|
||||
if !(0..w).contains(&x) || !(0..h).contains(&y) {
|
||||
return None;
|
||||
}
|
||||
let i = ((y * w + x) * 4) as usize;
|
||||
let px = shot.rgba.get(i..i + 3)?;
|
||||
Some((px[0], px[1], px[2]))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
fn shot() -> Screenshot {
|
||||
// 4×2 physical pixels at scale 2: pixel (x, y) has red = x, green = y.
|
||||
let mut rgba = Vec::new();
|
||||
for y in 0..2u8 {
|
||||
for x in 0..4u8 {
|
||||
rgba.extend([x, y, 9, 255]);
|
||||
}
|
||||
}
|
||||
Screenshot::new(rgba, iced::Size::new(4, 2), 2.0)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn samples_physical_pixels_from_logical_points() {
|
||||
let s = shot();
|
||||
assert_eq!(sample(&s, Point::new(1.0, 0.5), 0, 0), Some((2, 1, 9)));
|
||||
assert_eq!(sample(&s, Point::new(1.0, 0.5), 1, -1), Some((3, 0, 9)));
|
||||
assert_eq!(sample(&s, Point::new(1.9, 0.0), 1, 0), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn asks_for_one_screenshot_and_picks_after_it_arrives() {
|
||||
let mut e = Eyedropper::default();
|
||||
assert!(e.track(Point::new(0.0, 0.0)));
|
||||
assert!(!e.track(Point::new(1.0, 0.0)));
|
||||
assert_eq!(e.picked(), None);
|
||||
e.set_shot(shot());
|
||||
assert_eq!(e.picked(), Some((2, 0, 9)));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,212 @@
|
||||
//! Colour maths and formatting for the Select Color panel.
|
||||
//!
|
||||
//! Everything here is pure so it can be unit tested without a UI: HSB/RGB
|
||||
//! conversion, hex formatting and parsing, nearest-index lookup and the
|
||||
//! short names the panel shows.
|
||||
|
||||
use codec::types::Color as AcadColor;
|
||||
|
||||
/// Hue in degrees (0–360), saturation and brightness in 0–1.
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub struct Hsb {
|
||||
pub h: f32,
|
||||
pub s: f32,
|
||||
pub b: f32,
|
||||
}
|
||||
|
||||
impl Hsb {
|
||||
pub fn to_rgb(self) -> (u8, u8, u8) {
|
||||
hsb_to_rgb(self.h, self.s, self.b)
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert hue (degrees), saturation and brightness (0–1) to 8-bit RGB.
|
||||
pub fn hsb_to_rgb(h: f32, s: f32, b: f32) -> (u8, u8, u8) {
|
||||
let h = h.rem_euclid(360.0);
|
||||
let s = s.clamp(0.0, 1.0);
|
||||
let b = b.clamp(0.0, 1.0);
|
||||
let channel = |n: f32| {
|
||||
let k = (n + h / 60.0) % 6.0;
|
||||
let v = b - b * s * k.min(4.0 - k).clamp(0.0, 1.0);
|
||||
(v * 255.0).round() as u8
|
||||
};
|
||||
(channel(5.0), channel(3.0), channel(1.0))
|
||||
}
|
||||
|
||||
/// Convert 8-bit RGB to HSB. Grays have no hue; they report 0.
|
||||
pub fn rgb_to_hsb(r: u8, g: u8, b: u8) -> Hsb {
|
||||
let (r, g, b) = (
|
||||
f32::from(r) / 255.0,
|
||||
f32::from(g) / 255.0,
|
||||
f32::from(b) / 255.0,
|
||||
);
|
||||
let max = r.max(g).max(b);
|
||||
let min = r.min(g).min(b);
|
||||
let delta = max - min;
|
||||
let mut h = if delta == 0.0 {
|
||||
0.0
|
||||
} else if max == r {
|
||||
60.0 * (((g - b) / delta) % 6.0)
|
||||
} else if max == g {
|
||||
60.0 * ((b - r) / delta + 2.0)
|
||||
} else {
|
||||
60.0 * ((r - g) / delta + 4.0)
|
||||
};
|
||||
if h < 0.0 {
|
||||
h += 360.0;
|
||||
}
|
||||
let s = if max == 0.0 { 0.0 } else { delta / max };
|
||||
Hsb { h, s, b: max }
|
||||
}
|
||||
|
||||
/// Cyan, magenta, yellow and black in percent, by the plain device-free
|
||||
/// formula (no ICC profile): screen values, not press values.
|
||||
pub fn rgb_to_cmyk(r: u8, g: u8, b: u8) -> [f32; 4] {
|
||||
let max = r.max(g).max(b);
|
||||
if max == 0 {
|
||||
return [0.0, 0.0, 0.0, 100.0];
|
||||
}
|
||||
let max = f32::from(max);
|
||||
let ink = |v: u8| (1.0 - f32::from(v) / max) * 100.0;
|
||||
[ink(r), ink(g), ink(b), (1.0 - max / 255.0) * 100.0]
|
||||
}
|
||||
|
||||
pub fn cmyk_to_rgb([c, m, y, k]: [f32; 4]) -> (u8, u8, u8) {
|
||||
let white = 255.0 * (1.0 - k.clamp(0.0, 100.0) / 100.0);
|
||||
let channel = |ink: f32| (white * (1.0 - ink.clamp(0.0, 100.0) / 100.0)).round() as u8;
|
||||
(channel(c), channel(m), channel(y))
|
||||
}
|
||||
|
||||
/// RGB of an ACI entry, or `None` outside 1–255.
|
||||
pub fn aci_rgb(index: u8) -> Option<(u8, u8, u8)> {
|
||||
codec::types::aci_table::aci_to_rgb(index)
|
||||
}
|
||||
|
||||
/// The RGB a concrete colour displays as. Logical colours have none.
|
||||
pub fn color_rgb(color: AcadColor) -> Option<(u8, u8, u8)> {
|
||||
match color {
|
||||
AcadColor::Index(i) => aci_rgb(i),
|
||||
AcadColor::Rgb { r, g, b } => Some((r, g, b)),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// The closest index colour to an RGB value.
|
||||
pub fn nearest_aci(r: u8, g: u8, b: u8) -> u8 {
|
||||
codec::types::aci_table::nearest_aci(r, g, b)
|
||||
}
|
||||
|
||||
/// `#RRGGBB`, upper case — the same format the Options hex fields store.
|
||||
pub fn hex_string(r: u8, g: u8, b: u8) -> String {
|
||||
crate::app::config::rgb_to_hex([r, g, b])
|
||||
}
|
||||
|
||||
/// Parse `#RRGGBB`, `RRGGBB`, `#RGB` or `RGB`. Unlike `config::parse_hex`,
|
||||
/// which Options uses, this also accepts the short three-digit form.
|
||||
pub fn parse_hex(text: &str) -> Option<(u8, u8, u8)> {
|
||||
let digits = text.trim().trim_start_matches('#');
|
||||
if !digits.chars().all(|c| c.is_ascii_hexdigit()) {
|
||||
return None;
|
||||
}
|
||||
let expanded: String = match digits.len() {
|
||||
3 => digits.chars().flat_map(|c| [c, c]).collect(),
|
||||
6 => digits.to_string(),
|
||||
_ => return None,
|
||||
};
|
||||
let value = u32::from_str_radix(&expanded, 16).ok()?;
|
||||
Some(((value >> 16) as u8, (value >> 8) as u8, value as u8))
|
||||
}
|
||||
|
||||
/// The short name shown on the Current / New swatch.
|
||||
pub fn short_name(color: AcadColor) -> String {
|
||||
match color {
|
||||
AcadColor::Index(i) => format!("ACI {i}"),
|
||||
AcadColor::Rgb { r, g, b } => hex_string(r, g, b),
|
||||
AcadColor::ByLayer => "ByLayer".to_string(),
|
||||
AcadColor::ByBlock => "ByBlock".to_string(),
|
||||
AcadColor::None => "None".to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
/// One-line description for the panel's info line.
|
||||
pub fn describe(color: AcadColor) -> String {
|
||||
match color {
|
||||
AcadColor::Index(i) => {
|
||||
let (r, g, b) = aci_rgb(i).unwrap_or((128, 128, 128));
|
||||
format!("ACI {i} · RGB {r}, {g}, {b}")
|
||||
}
|
||||
AcadColor::Rgb { r, g, b } => format!("RGB {r}, {g}, {b} · {}", hex_string(r, g, b)),
|
||||
other => short_name(other),
|
||||
}
|
||||
}
|
||||
|
||||
/// Black or white text, whichever reads better on the given background.
|
||||
pub fn ink_is_dark(r: u8, g: u8, b: u8) -> bool {
|
||||
0.299 * f32::from(r) + 0.587 * f32::from(g) + 0.114 * f32::from(b) > 150.0
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn hsb_round_trips_primary_and_mixed_colours() {
|
||||
for (r, g, b) in [
|
||||
(255, 0, 0),
|
||||
(0, 255, 0),
|
||||
(0, 0, 255),
|
||||
(57, 191, 252),
|
||||
(120, 200, 80),
|
||||
(128, 128, 128),
|
||||
(0, 0, 0),
|
||||
(255, 255, 255),
|
||||
] {
|
||||
let hsb = rgb_to_hsb(r, g, b);
|
||||
assert_eq!(hsb.to_rgb(), (r, g, b), "round trip of {r},{g},{b}");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hue_of_pure_colours() {
|
||||
assert_eq!(rgb_to_hsb(255, 0, 0).h, 0.0);
|
||||
assert!((rgb_to_hsb(0, 255, 0).h - 120.0).abs() < 0.01);
|
||||
assert!((rgb_to_hsb(0, 0, 255).h - 240.0).abs() < 0.01);
|
||||
assert_eq!(rgb_to_hsb(80, 80, 80).s, 0.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hue_wraps_around() {
|
||||
assert_eq!(hsb_to_rgb(360.0, 1.0, 1.0), (255, 0, 0));
|
||||
assert_eq!(hsb_to_rgb(-120.0, 1.0, 1.0), hsb_to_rgb(240.0, 1.0, 1.0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn nearest_aci_comes_from_the_codec_table() {
|
||||
assert_eq!(nearest_aci(255, 0, 0), 1);
|
||||
assert_eq!(nearest_aci(250, 5, 5), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn short_names() {
|
||||
assert_eq!(short_name(AcadColor::Index(140)), "ACI 140");
|
||||
assert_eq!(
|
||||
short_name(AcadColor::Rgb {
|
||||
r: 57,
|
||||
g: 191,
|
||||
b: 252
|
||||
}),
|
||||
"#39BFFC"
|
||||
);
|
||||
assert_eq!(short_name(AcadColor::ByLayer), "ByLayer");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hex_formats_and_parses() {
|
||||
assert_eq!(hex_string(57, 191, 252), "#39BFFC");
|
||||
assert_eq!(parse_hex("#39bffc"), Some((57, 191, 252)));
|
||||
assert_eq!(parse_hex("39BFFC"), Some((57, 191, 252)));
|
||||
assert_eq!(parse_hex("#3bf"), Some((0x33, 0xBB, 0xFF)));
|
||||
assert_eq!(parse_hex("#39BFF"), None);
|
||||
assert_eq!(parse_hex("zzzzzz"), None);
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,17 @@
|
||||
//! properties panel and every style editor so colour selection looks and
|
||||
//! behaves the same everywhere.
|
||||
|
||||
pub mod book;
|
||||
pub mod document;
|
||||
pub mod editor;
|
||||
pub mod eyedropper;
|
||||
pub mod math;
|
||||
pub mod picker;
|
||||
pub mod widgets;
|
||||
|
||||
pub use math::nearest_aci;
|
||||
pub use picker::{ColorPicker, ColorPickerMsg, Outcome};
|
||||
|
||||
use crate::app::Message;
|
||||
use crate::t;
|
||||
use crate::ui::properties::acad_color_display;
|
||||
@@ -34,42 +45,6 @@ pub fn color_to_aci_string(c: AcadColor) -> String {
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert an Iced colour chosen by `iced_aw::ColorPicker` into a DWG true
|
||||
/// colour. ACI-only destinations map it to their closest indexed colour later.
|
||||
pub fn iced_to_acad_color(color: Color) -> AcadColor {
|
||||
// Truncate the way the picker labels its channels, so the stored colour
|
||||
// is the one the dialog showed; rounding drifted dragged values by one
|
||||
// (#1621).
|
||||
let byte = |c: f32| (c * 255.0) as u8;
|
||||
AcadColor::Rgb {
|
||||
r: byte(color.r),
|
||||
g: byte(color.g),
|
||||
b: byte(color.b),
|
||||
}
|
||||
}
|
||||
|
||||
/// Return the closest CAD Color Index for an RGB colour.
|
||||
pub fn nearest_aci(r: u8, g: u8, b: u8) -> u8 {
|
||||
let mut best = 7;
|
||||
let mut best_distance = u32::MAX;
|
||||
|
||||
for index in 1..=255 {
|
||||
let Some((ar, ag, ab)) = codec::types::aci_table::aci_to_rgb(index) else {
|
||||
continue;
|
||||
};
|
||||
let dr = i32::from(r) - i32::from(ar);
|
||||
let dg = i32::from(g) - i32::from(ag);
|
||||
let db = i32::from(b) - i32::from(ab);
|
||||
let distance = (dr * dr + dg * dg + db * db) as u32;
|
||||
if distance < best_distance {
|
||||
best = index;
|
||||
best_distance = distance;
|
||||
}
|
||||
}
|
||||
|
||||
best
|
||||
}
|
||||
|
||||
/// Decode an ACI integer string back into an `AcadColor`.
|
||||
pub fn aci_string_to_color(s: &str) -> AcadColor {
|
||||
match s.trim().parse::<i16>().unwrap_or(256) {
|
||||
@@ -349,174 +324,3 @@ pub fn drop_down_below<'a>(
|
||||
}
|
||||
dd.into()
|
||||
}
|
||||
|
||||
/// Full CAD indexed-colour page used by the standalone Select Color dialog.
|
||||
///
|
||||
/// This only edits the pending colour. The caller commits it with
|
||||
/// `Message::ColorWindowPick`, so choosing a swatch does not immediately close
|
||||
/// the dialog.
|
||||
pub fn index_color_page<'a>(
|
||||
current: AcadColor,
|
||||
recent_colors: &'a [AcadColor],
|
||||
) -> Element<'a, Message> {
|
||||
let swatch_button = |color: AcadColor, size: f32| -> Element<'a, Message> {
|
||||
let (bg, _) = acad_color_display(color);
|
||||
let selected = current == color;
|
||||
|
||||
button(
|
||||
text("")
|
||||
.width(Length::Fixed(size))
|
||||
.height(Length::Fixed(size)),
|
||||
)
|
||||
.on_press(Message::ColorPickerColorChanged(color))
|
||||
.style(move |theme: &Theme, status| {
|
||||
let hovered = matches!(status, button::Status::Hovered | button::Status::Pressed);
|
||||
|
||||
button::Style {
|
||||
background: Some(Background::Color(bg)),
|
||||
border: Border {
|
||||
color: if selected || hovered {
|
||||
theme.palette().primary.base.color
|
||||
} else {
|
||||
theme.palette().background.neutral.color
|
||||
},
|
||||
width: if selected {
|
||||
2.5
|
||||
} else if hovered {
|
||||
1.5
|
||||
} else {
|
||||
1.0
|
||||
},
|
||||
radius: 1.0.into(),
|
||||
},
|
||||
..Default::default()
|
||||
}
|
||||
})
|
||||
.padding(0)
|
||||
.into()
|
||||
};
|
||||
|
||||
// The first standard indexed CAD colours.
|
||||
let mut standard = row![].spacing(4);
|
||||
for idx in 1u8..=9 {
|
||||
standard = standard.push(swatch_button(AcadColor::Index(idx), 22.0));
|
||||
}
|
||||
|
||||
// ACI 10-249.
|
||||
//
|
||||
// Each group of ten indices belongs together. Arrange the 24 colour
|
||||
// families horizontally and their ten shade/intensity variants vertically.
|
||||
// This makes the palette read as a colour spectrum instead of a raw numeric
|
||||
// sequence.
|
||||
let mut palette_rows = column![].spacing(2);
|
||||
|
||||
for variant in 0u8..10 {
|
||||
let mut palette_row = row![].spacing(2);
|
||||
|
||||
for family in 1u8..=24 {
|
||||
let idx = family * 10 + variant;
|
||||
palette_row = palette_row.push(swatch_button(AcadColor::Index(idx), 16.0));
|
||||
}
|
||||
|
||||
palette_rows = palette_rows.push(palette_row);
|
||||
}
|
||||
|
||||
// ACI 250-255 are the grayscale entries and are shown separately.
|
||||
let mut grayscale = row![].spacing(4);
|
||||
|
||||
for idx in 250u8..=255 {
|
||||
grayscale = grayscale.push(swatch_button(AcadColor::Index(idx), 22.0));
|
||||
}
|
||||
|
||||
let recent: Element<'a, Message> = if recent_colors.is_empty() {
|
||||
text(crate::t!("No recent colors yet"))
|
||||
.size(10)
|
||||
.style(|theme: &Theme| iced::widget::text::Style {
|
||||
color: Some(theme.palette().background.base.text.scale_alpha(0.55)),
|
||||
})
|
||||
.into()
|
||||
} else {
|
||||
let mut recent_row = row![].spacing(4);
|
||||
|
||||
for &color in recent_colors.iter().take(12) {
|
||||
recent_row = recent_row.push(swatch_button(color, 22.0));
|
||||
}
|
||||
|
||||
recent_row.into()
|
||||
};
|
||||
|
||||
let selected_text = match current {
|
||||
AcadColor::Index(i) => {
|
||||
let (r, g, b) = codec::types::aci_table::aci_to_rgb(i).unwrap_or((128, 128, 128));
|
||||
format!("ACI {i} RGB {r}, {g}, {b}")
|
||||
}
|
||||
AcadColor::Rgb { r, g, b } => crate::tf!("True Color RGB {r}, {g}, {b}").into_owned(),
|
||||
AcadColor::ByLayer => t!("ByLayer").into_owned(),
|
||||
AcadColor::ByBlock => t!("ByBlock").into_owned(),
|
||||
AcadColor::None => t!("None").into_owned(),
|
||||
};
|
||||
|
||||
let tabs = row![
|
||||
button(text(crate::t!("Index Color")).size(12))
|
||||
.style(button::primary)
|
||||
.padding([5, 14]),
|
||||
button(text(crate::t!("True Color")).size(12))
|
||||
.on_press(Message::ColorPickerTabChanged(
|
||||
crate::app::ColorPickerTab::TrueColor
|
||||
))
|
||||
.style(button::secondary)
|
||||
.padding([5, 14]),
|
||||
]
|
||||
.spacing(4);
|
||||
|
||||
let actions = container(
|
||||
row![
|
||||
button(text(crate::t!("Cancel")).size(11))
|
||||
.on_press(Message::CloseColorPicker)
|
||||
.style(button::secondary)
|
||||
.padding([5, 14]),
|
||||
button(text(crate::t!("OK")).size(11))
|
||||
.on_press(Message::ColorWindowPick(current))
|
||||
.style(button::primary)
|
||||
.padding([5, 18]),
|
||||
]
|
||||
.spacing(8),
|
||||
)
|
||||
.width(Length::Fill)
|
||||
.align_x(iced::alignment::Horizontal::Right);
|
||||
|
||||
container(
|
||||
column![
|
||||
tabs,
|
||||
text(crate::t!("Standard colors")).size(11),
|
||||
standard,
|
||||
text(crate::t!("CAD Color Index (ACI) 10–249")).size(11),
|
||||
text(crate::t!("Color family → · Shade / intensity ↓"))
|
||||
.size(9)
|
||||
.style(|theme: &Theme| iced::widget::text::Style {
|
||||
color: Some(theme.palette().background.base.text.scale_alpha(0.65)),
|
||||
}),
|
||||
container(palette_rows)
|
||||
.padding(6)
|
||||
.style(|theme: &Theme| container::Style {
|
||||
background: Some(Background::Color(theme.palette().background.weakest.color)),
|
||||
border: Border {
|
||||
color: theme.palette().background.neutral.color,
|
||||
width: 1.0,
|
||||
radius: 3.0.into(),
|
||||
},
|
||||
..Default::default()
|
||||
}),
|
||||
text(crate::t!("Grayscale 250–255")).size(11),
|
||||
grayscale,
|
||||
text(crate::t!("Recently used")).size(11),
|
||||
recent,
|
||||
text(selected_text).size(11),
|
||||
actions,
|
||||
]
|
||||
.spacing(9),
|
||||
)
|
||||
.padding(10)
|
||||
.width(Length::Fixed(470.0))
|
||||
.into()
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,258 @@
|
||||
//! Saturation/brightness field for one hue.
|
||||
//!
|
||||
//! Drawn as three layered quads instead of pixel by pixel: the solid hue, a
|
||||
//! white-to-transparent wash from left to right, and a transparent-to-black
|
||||
//! wash from top to bottom. Saturation runs left to right, brightness bottom
|
||||
//! to top. Dragging anywhere moves the marker.
|
||||
|
||||
use iced::advanced::layout::{self, Layout};
|
||||
use iced::advanced::widget::{self, tree, Widget};
|
||||
use iced::advanced::{mouse, renderer, Shell};
|
||||
use iced::gradient::Linear;
|
||||
use iced::{
|
||||
Background, Border, Color, Element, Event, Gradient, Length, Point, Radians, Rectangle,
|
||||
Renderer, Size, Theme,
|
||||
};
|
||||
|
||||
const RADIUS: f32 = 5.0;
|
||||
const MARKER: f32 = 16.0;
|
||||
|
||||
pub struct ColorArea<'a, Message> {
|
||||
/// Fully saturated, full-brightness colour of the current hue.
|
||||
hue_color: Color,
|
||||
/// The colour under the marker.
|
||||
marker_color: Color,
|
||||
saturation: f32,
|
||||
brightness: f32,
|
||||
on_change: Box<dyn Fn(f32, f32) -> Message + 'a>,
|
||||
width: Length,
|
||||
height: Length,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct State {
|
||||
dragging: bool,
|
||||
}
|
||||
|
||||
impl<'a, Message> ColorArea<'a, Message> {
|
||||
/// `saturation` and `brightness` are 0–1; `on_change` receives the same.
|
||||
pub fn new(
|
||||
hue_color: Color,
|
||||
marker_color: Color,
|
||||
saturation: f32,
|
||||
brightness: f32,
|
||||
on_change: impl Fn(f32, f32) -> Message + 'a,
|
||||
) -> Self {
|
||||
Self {
|
||||
hue_color,
|
||||
marker_color,
|
||||
saturation: saturation.clamp(0.0, 1.0),
|
||||
brightness: brightness.clamp(0.0, 1.0),
|
||||
on_change: Box::new(on_change),
|
||||
width: Length::Fill,
|
||||
height: Length::Fixed(150.0),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn height(mut self, height: impl Into<Length>) -> Self {
|
||||
self.height = height.into();
|
||||
self
|
||||
}
|
||||
|
||||
fn values_at(bounds: Rectangle, cursor: Point) -> (f32, f32) {
|
||||
let s = ((cursor.x - bounds.x) / bounds.width.max(1.0)).clamp(0.0, 1.0);
|
||||
let b = 1.0 - ((cursor.y - bounds.y) / bounds.height.max(1.0)).clamp(0.0, 1.0);
|
||||
(s, b)
|
||||
}
|
||||
}
|
||||
|
||||
impl<Message> Widget<Message, Theme, Renderer> for ColorArea<'_, Message> {
|
||||
fn tag(&self) -> tree::Tag {
|
||||
tree::Tag::of::<State>()
|
||||
}
|
||||
|
||||
fn state(&self) -> tree::State {
|
||||
tree::State::new(State::default())
|
||||
}
|
||||
|
||||
fn size(&self) -> Size<Length> {
|
||||
Size::new(self.width, self.height)
|
||||
}
|
||||
|
||||
fn layout(
|
||||
&mut self,
|
||||
_tree: &mut widget::Tree,
|
||||
_renderer: &Renderer,
|
||||
limits: &layout::Limits,
|
||||
) -> layout::Node {
|
||||
layout::atomic(limits, self.width, self.height)
|
||||
}
|
||||
|
||||
fn update(
|
||||
&mut self,
|
||||
tree: &mut widget::Tree,
|
||||
event: &Event,
|
||||
layout: Layout<'_>,
|
||||
cursor: mouse::Cursor,
|
||||
_renderer: &Renderer,
|
||||
shell: &mut Shell<'_, Message>,
|
||||
_viewport: &Rectangle,
|
||||
) {
|
||||
let state = tree.state.downcast_mut::<State>();
|
||||
let bounds = layout.bounds();
|
||||
let publish = |shell: &mut Shell<'_, Message>, position: Point| {
|
||||
let (s, b) = Self::values_at(bounds, position);
|
||||
shell.publish((self.on_change)(s, b));
|
||||
shell.capture_event();
|
||||
};
|
||||
match event {
|
||||
Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) => {
|
||||
if let Some(position) = cursor.position_over(bounds) {
|
||||
state.dragging = true;
|
||||
publish(shell, position);
|
||||
}
|
||||
}
|
||||
Event::Mouse(mouse::Event::CursorMoved { .. }) if state.dragging => {
|
||||
if let Some(position) = cursor.position() {
|
||||
publish(shell, position);
|
||||
}
|
||||
}
|
||||
Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left)) if state.dragging => {
|
||||
state.dragging = false;
|
||||
shell.capture_event();
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
fn mouse_interaction(
|
||||
&self,
|
||||
tree: &widget::Tree,
|
||||
layout: Layout<'_>,
|
||||
cursor: mouse::Cursor,
|
||||
_viewport: &Rectangle,
|
||||
_renderer: &Renderer,
|
||||
) -> mouse::Interaction {
|
||||
if tree.state.downcast_ref::<State>().dragging || cursor.is_over(layout.bounds()) {
|
||||
mouse::Interaction::Crosshair
|
||||
} else {
|
||||
mouse::Interaction::default()
|
||||
}
|
||||
}
|
||||
|
||||
fn draw(
|
||||
&self,
|
||||
_tree: &widget::Tree,
|
||||
renderer: &mut Renderer,
|
||||
_theme: &Theme,
|
||||
_style: &renderer::Style,
|
||||
layout: Layout<'_>,
|
||||
_cursor: mouse::Cursor,
|
||||
_viewport: &Rectangle,
|
||||
) {
|
||||
use iced::advanced::Renderer as _;
|
||||
|
||||
let bounds = layout.bounds();
|
||||
let rounded = renderer::Quad {
|
||||
bounds,
|
||||
border: Border {
|
||||
radius: RADIUS.into(),
|
||||
..Border::default()
|
||||
},
|
||||
..renderer::Quad::default()
|
||||
};
|
||||
let wash = |angle: f32, from: Color, to: Color| {
|
||||
Background::Gradient(Gradient::Linear(
|
||||
Linear::new(Radians(angle))
|
||||
.add_stop(0.0, from)
|
||||
.add_stop(1.0, to),
|
||||
))
|
||||
};
|
||||
|
||||
renderer.fill_quad(rounded, self.hue_color);
|
||||
renderer.fill_quad(
|
||||
rounded,
|
||||
wash(
|
||||
std::f32::consts::FRAC_PI_2,
|
||||
Color::WHITE,
|
||||
Color {
|
||||
a: 0.0,
|
||||
..Color::WHITE
|
||||
},
|
||||
),
|
||||
);
|
||||
renderer.fill_quad(
|
||||
renderer::Quad {
|
||||
border: Border {
|
||||
color: Color::from_rgba(0.0, 0.0, 0.0, 0.5),
|
||||
width: 1.0,
|
||||
radius: RADIUS.into(),
|
||||
},
|
||||
..rounded
|
||||
},
|
||||
wash(
|
||||
std::f32::consts::PI,
|
||||
Color {
|
||||
a: 0.0,
|
||||
..Color::BLACK
|
||||
},
|
||||
Color::BLACK,
|
||||
),
|
||||
);
|
||||
|
||||
let centre = Point::new(
|
||||
bounds.x + bounds.width * self.saturation,
|
||||
bounds.y + bounds.height * (1.0 - self.brightness),
|
||||
);
|
||||
let marker = |size: f32| Rectangle {
|
||||
x: centre.x - size / 2.0,
|
||||
y: centre.y - size / 2.0,
|
||||
width: size,
|
||||
height: size,
|
||||
};
|
||||
renderer.fill_quad(
|
||||
renderer::Quad {
|
||||
bounds: marker(MARKER + 2.0),
|
||||
border: Border {
|
||||
radius: ((MARKER + 2.0) / 2.0).into(),
|
||||
..Border::default()
|
||||
},
|
||||
..renderer::Quad::default()
|
||||
},
|
||||
Color::from_rgba(0.0, 0.0, 0.0, 0.55),
|
||||
);
|
||||
renderer.fill_quad(
|
||||
renderer::Quad {
|
||||
bounds: marker(MARKER),
|
||||
border: Border {
|
||||
color: Color::WHITE,
|
||||
width: 2.0,
|
||||
radius: (MARKER / 2.0).into(),
|
||||
},
|
||||
..renderer::Quad::default()
|
||||
},
|
||||
self.marker_color,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, Message: 'a> From<ColorArea<'a, Message>> for Element<'a, Message> {
|
||||
fn from(area: ColorArea<'a, Message>) -> Self {
|
||||
Element::new(area)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn corners_map_to_saturation_and_brightness() {
|
||||
let bounds = Rectangle::new(Point::new(10.0, 20.0), Size::new(200.0, 100.0));
|
||||
let at = |x, y| ColorArea::<()>::values_at(bounds, Point::new(x, y));
|
||||
assert_eq!(at(10.0, 20.0), (0.0, 1.0));
|
||||
assert_eq!(at(210.0, 120.0), (1.0, 0.0));
|
||||
assert_eq!(at(110.0, 70.0), (0.5, 0.5));
|
||||
assert_eq!(at(-5.0, 500.0), (0.0, 0.0));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
//! Widgets the Select Color panel needs that iced does not provide.
|
||||
|
||||
mod color_area;
|
||||
|
||||
pub use color_area::ColorArea;
|
||||
@@ -62,6 +62,7 @@ static LAY_ULK: &[u8] = include_bytes!("../../assets/icons/layers/layulk.svg");
|
||||
// All are black-on-transparent; recolour them at the call site with [`tinted`].
|
||||
pub static CHECK: &[u8] = include_bytes!("../../assets/icons/ui/check.svg");
|
||||
pub static CLOSE: &[u8] = include_bytes!("../../assets/icons/ui/close.svg");
|
||||
pub static EYEDROPPER: &[u8] = include_bytes!("../../assets/icons/ui/eyedropper.svg");
|
||||
pub static PLUS: &[u8] = include_bytes!("../../assets/icons/ui/plus.svg");
|
||||
pub static REFRESH: &[u8] = include_bytes!("../../assets/icons/ui/refresh.svg");
|
||||
pub static SWAP: &[u8] = include_bytes!("../../assets/icons/ui/swap.svg");
|
||||
@@ -80,6 +81,8 @@ pub static PIN_ACTIVE: &[u8] = include_bytes!("../../assets/icons/ui/pin_active.
|
||||
pub static SPLIT_V: &[u8] = include_bytes!("../../assets/icons/ui/split_v.svg");
|
||||
pub static SPLIT_H: &[u8] = include_bytes!("../../assets/icons/ui/split_h.svg");
|
||||
pub static GRID: &[u8] = include_bytes!("../../assets/icons/ui/grid.svg");
|
||||
pub static LIST: &[u8] = include_bytes!("../../assets/icons/ui/list.svg");
|
||||
pub static SEARCH: &[u8] = include_bytes!("../../assets/icons/ui/search.svg");
|
||||
pub static PREVIEW: &[u8] = include_bytes!("../../assets/icons/ui/preview.svg");
|
||||
pub static SNAP: &[u8] = include_bytes!("../../assets/icons/ui/snap.svg");
|
||||
pub static DOC_NEW: &[u8] = include_bytes!("../../assets/icons/ui/doc_new.svg");
|
||||
|
||||
@@ -157,12 +157,14 @@ fn divider<'a>() -> Element<'a, Message> {
|
||||
.into()
|
||||
}
|
||||
|
||||
/// `shown` overrides the swatch, so ByLayer can show the layer's colour.
|
||||
fn logical_row<'a>(
|
||||
color: AcadColor,
|
||||
cur_color: AcadColor,
|
||||
label: &'static str,
|
||||
shown: Option<iced::Color>,
|
||||
) -> Element<'a, Message> {
|
||||
let (bg, _) = acad_color_display(color);
|
||||
let bg = shown.unwrap_or_else(|| acad_color_display(color).0);
|
||||
let selected = color == cur_color;
|
||||
|
||||
let swatch = container(
|
||||
@@ -212,13 +214,15 @@ fn more_colors_row<'a>(active_color: AcadColor) -> Element<'a, Message> {
|
||||
}
|
||||
|
||||
/// Builds the complete 5-section color dropdown panel element.
|
||||
/// `layer_color` is the current layer's colour, shown on the ByLayer row.
|
||||
pub fn color_dropdown_panel<'a>(
|
||||
active_color: AcadColor,
|
||||
recent_colors: &[AcadColor],
|
||||
layer_color: Option<iced::Color>,
|
||||
) -> Element<'a, Message> {
|
||||
// 1. ByLayer and ByBlock
|
||||
let by_layer = logical_row(AcadColor::ByLayer, active_color, "ByLayer");
|
||||
let by_block = logical_row(AcadColor::ByBlock, active_color, "ByBlock");
|
||||
let by_layer = logical_row(AcadColor::ByLayer, active_color, "ByLayer", layer_color);
|
||||
let by_block = logical_row(AcadColor::ByBlock, active_color, "ByBlock", None);
|
||||
let section1 = column![by_layer, by_block].spacing(2);
|
||||
|
||||
// 2. 45-swatch Quick Pick Grid (5 rows × 9 columns)
|
||||
|
||||
+31
-1
@@ -88,6 +88,9 @@ pub struct Ribbon {
|
||||
pub layer_filter: String,
|
||||
/// Active object color — ACI / ByLayer / ByBlock.
|
||||
pub active_color: AcadColor,
|
||||
/// Colour-book identity (`BOOK$NAME`) of a colour picked from a colour
|
||||
/// book, with the colour it was picked as; see `active_color_name`.
|
||||
pub active_color_book: Option<(AcadColor, String)>,
|
||||
/// Active linetype override ("ByLayer", "Continuous", …).
|
||||
pub active_linetype: String,
|
||||
/// Active lineweight.
|
||||
@@ -145,6 +148,14 @@ pub struct LayerInfo {
|
||||
pub vp_frozen: Option<(usize, bool)>,
|
||||
}
|
||||
|
||||
/// The colour of layer `name` in the ribbon's layer list.
|
||||
pub(super) fn layer_color(layers: &[LayerInfo], name: &str) -> Option<Color> {
|
||||
layers
|
||||
.iter()
|
||||
.find(|layer| layer.name == name)
|
||||
.map(|layer| layer.color)
|
||||
}
|
||||
|
||||
/// Full-screen backdrop for an open ribbon dropdown: closes the dropdown when
|
||||
/// the user clicks outside the panel. (Cursor motion leaking to the viewport
|
||||
/// beneath is blocked in `on_viewport_move`, not here — iced 0.14's mouse_area
|
||||
@@ -183,6 +194,20 @@ fn position_ribbon_dropdown<'a>(
|
||||
}
|
||||
|
||||
impl Ribbon {
|
||||
/// The current layer's colour: what ByLayer looks like for new objects.
|
||||
pub fn active_layer_color(&self) -> Option<Color> {
|
||||
layer_color(&self.layer_infos, &self.active_layer)
|
||||
}
|
||||
|
||||
/// The colour-book identity of the active colour, while the active colour
|
||||
/// is still the one picked from the book.
|
||||
pub fn active_color_name(&self) -> Option<String> {
|
||||
self.active_color_book
|
||||
.as_ref()
|
||||
.filter(|(color, _)| *color == self.active_color)
|
||||
.map(|(_, name)| name.clone())
|
||||
}
|
||||
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
modules: all_ribbon_modules(),
|
||||
@@ -207,6 +232,7 @@ impl Ribbon {
|
||||
layer_infos: vec![],
|
||||
layer_filter: String::new(),
|
||||
active_color: AcadColor::ByLayer,
|
||||
active_color_book: None,
|
||||
active_linetype: "ByLayer".to_string(),
|
||||
active_lineweight: LineWeight::ByLayer,
|
||||
available_linetypes: vec![LinetypeItem {
|
||||
@@ -1293,7 +1319,11 @@ impl Ribbon {
|
||||
win: (f32, f32),
|
||||
recent_colors: &[AcadColor],
|
||||
) -> Option<Element<'_, Message>> {
|
||||
let panel = color_dropdown::color_dropdown_panel(self.active_color, recent_colors);
|
||||
let panel = color_dropdown::color_dropdown_panel(
|
||||
self.active_color,
|
||||
recent_colors,
|
||||
self.active_layer_color(),
|
||||
);
|
||||
|
||||
let (align_right, h_pad, top) =
|
||||
self.dd_anchor(PROP_COLOR_ID, color_dropdown::PANEL_W, win.0);
|
||||
|
||||
@@ -1193,7 +1193,10 @@ pub(super) fn render_large<'a>(item: &RibbonItem, ctx: &RenderCtx<'_>) -> Elemen
|
||||
.width(Length::Fixed(PROP_W))
|
||||
};
|
||||
|
||||
let (color_swatch, _) = acad_color_display(active_color);
|
||||
// ByLayer shows the current layer's colour.
|
||||
let color_swatch = super::layer_color(layer_infos, active_layer)
|
||||
.filter(|_| active_color == AcadColor::ByLayer)
|
||||
.unwrap_or_else(|| acad_color_display(active_color).0);
|
||||
let color_row = prop_row(
|
||||
crate::ui::color_select::color_display_name(active_color),
|
||||
PROP_COLOR_ID,
|
||||
|
||||
+36
-92
@@ -108,6 +108,8 @@ pub struct Folders {
|
||||
/// Where `.shx` / `.ttf` fonts dropped in by the user (or downloaded for
|
||||
/// a drawing) are found.
|
||||
pub fonts: Option<String>,
|
||||
/// Where `.acb` colour books are kept and loaded ones copied to.
|
||||
pub color_books: Option<String>,
|
||||
}
|
||||
|
||||
/// Values read from the current drawing's header rather than from preferences.
|
||||
@@ -165,22 +167,17 @@ impl<T> fmt::Display for Labelled<T> {
|
||||
}
|
||||
}
|
||||
|
||||
/// A background swatch that opens the colour wheel when clicked.
|
||||
/// A colour swatch that opens the Select Color panel when clicked.
|
||||
///
|
||||
/// The wheel sits alongside the hex field rather than replacing it: typing
|
||||
/// The swatch sits beside the hex field rather than replacing it: typing
|
||||
/// `#1E1E1E` stays the fastest way to reproduce an exact colour, while the
|
||||
/// wheel is for choosing one by eye.
|
||||
fn bg_swatch_picker<'a>(
|
||||
target: crate::app::BgTarget,
|
||||
rgb: [u8; 3],
|
||||
open: Option<crate::app::BgTarget>,
|
||||
) -> Element<'a, Message> {
|
||||
let colour = iced::Color::from_rgb8(rgb[0], rgb[1], rgb[2]);
|
||||
/// panel is for choosing one by eye.
|
||||
fn color_swatch<'a>(target: crate::app::UiColor, [r, g, b]: [u8; 3]) -> Element<'a, Message> {
|
||||
let swatch = container(Space::new())
|
||||
.width(28)
|
||||
.height(22)
|
||||
.style(move |theme: &Theme| container::Style {
|
||||
background: Some(Background::Color(colour)),
|
||||
background: Some(Background::Color(iced::Color::from_rgb8(r, g, b))),
|
||||
border: Border {
|
||||
color: theme.palette().background.strong.color,
|
||||
width: 1.0,
|
||||
@@ -188,18 +185,14 @@ fn bg_swatch_picker<'a>(
|
||||
},
|
||||
..Default::default()
|
||||
});
|
||||
let underlay = button(swatch)
|
||||
.on_press(Message::BgPickerOpen(target))
|
||||
button(swatch)
|
||||
.on_press(Message::OpenColorWindow(
|
||||
crate::app::ColorPickTarget::Interface(target),
|
||||
codec::types::Color::Rgb { r, g, b },
|
||||
))
|
||||
.padding(0)
|
||||
.style(button::text);
|
||||
iced_aw::ColorPicker::new(
|
||||
open == Some(target),
|
||||
colour,
|
||||
underlay,
|
||||
Message::BgPickerCancel,
|
||||
Message::BgPickerSubmit,
|
||||
)
|
||||
.into()
|
||||
.style(button::text)
|
||||
.into()
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
@@ -235,7 +228,6 @@ pub fn view_window<'a>(
|
||||
model_bg_input: &'a str,
|
||||
paper_bg_input: &'a str,
|
||||
desk_bg_input: &'a str,
|
||||
bg_picker: Option<crate::app::BgTarget>,
|
||||
dirty: bool,
|
||||
close_confirm: bool,
|
||||
sizing: crate::ui::modal::ModalSizing,
|
||||
@@ -315,18 +307,8 @@ pub fn view_window<'a>(
|
||||
|
||||
let mut color_controls = column![].spacing(8);
|
||||
for (index, (label, color)) in colors.into_iter().enumerate() {
|
||||
let swatch = container(Space::new())
|
||||
.width(28)
|
||||
.height(22)
|
||||
.style(move |theme: &Theme| container::Style {
|
||||
background: Some(Background::Color(color)),
|
||||
border: Border {
|
||||
color: theme.palette().background.strong.color,
|
||||
width: 1.0,
|
||||
radius: 3.0.into(),
|
||||
},
|
||||
..Default::default()
|
||||
});
|
||||
let [r, g, bl, _] = color.into_rgba8();
|
||||
let swatch = color_swatch(crate::app::UiColor::Theme(index), [r, g, bl]);
|
||||
color_controls = color_controls.push(
|
||||
row![
|
||||
text(label).size(12).width(110),
|
||||
@@ -536,61 +518,18 @@ pub fn view_window<'a>(
|
||||
.spacing(0)
|
||||
.width(sizing.width);
|
||||
|
||||
let crosshair_rgb = crosshair_color.unwrap_or([255, 255, 255]);
|
||||
let crosshair_swatch =
|
||||
container(Space::new())
|
||||
.width(28)
|
||||
.height(22)
|
||||
.style(move |theme: &Theme| container::Style {
|
||||
background: Some(Background::Color(iced::Color::from_rgb8(
|
||||
crosshair_rgb[0],
|
||||
crosshair_rgb[1],
|
||||
crosshair_rgb[2],
|
||||
))),
|
||||
border: Border {
|
||||
color: theme.palette().background.strong.color,
|
||||
width: 1.0,
|
||||
radius: 3.0.into(),
|
||||
},
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
let command_rgb = command_text_color.unwrap_or([220, 220, 220]);
|
||||
let command_swatch =
|
||||
container(Space::new())
|
||||
.width(28)
|
||||
.height(22)
|
||||
.style(move |theme: &Theme| container::Style {
|
||||
background: Some(Background::Color(iced::Color::from_rgb8(
|
||||
command_rgb[0],
|
||||
command_rgb[1],
|
||||
command_rgb[2],
|
||||
))),
|
||||
border: Border {
|
||||
color: theme.palette().background.strong.color,
|
||||
width: 1.0,
|
||||
radius: 3.0.into(),
|
||||
},
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
let snap_rgb = snap_marker_color.unwrap_or([255, 230, 26]);
|
||||
let snap_swatch = container(Space::new())
|
||||
.width(28)
|
||||
.height(22)
|
||||
.style(move |theme: &Theme| container::Style {
|
||||
background: Some(Background::Color(iced::Color::from_rgb8(
|
||||
snap_rgb[0],
|
||||
snap_rgb[1],
|
||||
snap_rgb[2],
|
||||
))),
|
||||
border: Border {
|
||||
color: theme.palette().background.strong.color,
|
||||
width: 1.0,
|
||||
radius: 3.0.into(),
|
||||
},
|
||||
..Default::default()
|
||||
});
|
||||
let crosshair_swatch = color_swatch(
|
||||
crate::app::UiColor::Crosshair,
|
||||
crosshair_color.unwrap_or([255, 255, 255]),
|
||||
);
|
||||
let command_swatch = color_swatch(
|
||||
crate::app::UiColor::CommandText,
|
||||
command_text_color.unwrap_or([220, 220, 220]),
|
||||
);
|
||||
let snap_swatch = color_swatch(
|
||||
crate::app::UiColor::SnapMarker,
|
||||
snap_marker_color.unwrap_or([255, 230, 26]),
|
||||
);
|
||||
|
||||
let model_bg_rgb = model_space
|
||||
.custom_bg
|
||||
@@ -750,7 +689,7 @@ pub fn view_window<'a>(
|
||||
display = display.push(Space::new().height(10)).push(
|
||||
row![
|
||||
text(crate::t!("Model background")).size(12).width(140),
|
||||
bg_swatch_picker(crate::app::BgTarget::Model, model_bg_rgb, bg_picker),
|
||||
color_swatch(crate::app::UiColor::ModelBackground, model_bg_rgb),
|
||||
text_input("#RRGGBB", model_bg_input)
|
||||
.on_input(Message::ModelSpaceBgChanged)
|
||||
.width(150),
|
||||
@@ -763,7 +702,7 @@ pub fn view_window<'a>(
|
||||
display = display.push(Space::new().height(10)).push(
|
||||
row![
|
||||
text(crate::t!("Paper background")).size(12).width(140),
|
||||
bg_swatch_picker(crate::app::BgTarget::Paper, paper_bg_rgb, bg_picker),
|
||||
color_swatch(crate::app::UiColor::PaperBackground, paper_bg_rgb),
|
||||
text_input("#RRGGBB", paper_bg_input)
|
||||
.on_input(Message::PaperSpaceBgChanged)
|
||||
.width(150),
|
||||
@@ -775,7 +714,7 @@ pub fn view_window<'a>(
|
||||
display = display.push(Space::new().height(10)).push(
|
||||
row![
|
||||
text(crate::t!("Desk surround")).size(12).width(140),
|
||||
bg_swatch_picker(crate::app::BgTarget::Desk, desk_bg_rgb, bg_picker),
|
||||
color_swatch(crate::app::UiColor::DeskBackground, desk_bg_rgb),
|
||||
text_input("#RRGGBB", desk_bg_input)
|
||||
.on_input(Message::DeskSpaceBgChanged)
|
||||
.width(150),
|
||||
@@ -1853,6 +1792,11 @@ pub fn view_window<'a>(
|
||||
folder_row(crate::t!("Autosave files"), folders.autosave.clone()),
|
||||
Space::new().height(12),
|
||||
folder_row(crate::t!("Fonts"), folders.fonts.clone()),
|
||||
Space::new().height(12),
|
||||
folder_row(
|
||||
crate::t!("Color book locations"),
|
||||
folders.color_books.clone()
|
||||
),
|
||||
Space::new().height(16),
|
||||
text(crate::t!("Default template for new drawings")).size(12),
|
||||
Space::new().height(4),
|
||||
|
||||
Reference in new issue
Block a user